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

@ai-switch/tauri-plugin-devkit

v0.1.1

Published

Node tooling for validating, building and packaging APLG plugins.

Readme

@ai-switch/tauri-plugin-devkit

APLG 插件的 Node 开发工具包。单向依赖 @ai-switch/tauri-plugin-runtime 公共协议入口,不依赖 AI Switch 应用源码或 Tauri。

当前为 0.1.1,已发布到 npm。D1–D8 已交付源码/产物校验、只读归档 inspect、CLI、Vite Node alias、握手 bootstrap、浏览器类型适配、可复现且不覆盖已有文件的 .aplg pack、无副作用的 vanilla-ts init 模板,以及显式内存测试宿主和仅 loopback 的开发预览。D9 协调 CI 与受控发布入口已准备;0.1.0 已用一次性 granular token 完成首发,trusted publisher 已配置。

使用已构建的本地包

需要 Node ^22.12.0 || ^24.0.0 || >=26.0.0、pnpm 10.12.4。从仓库根目录执行:

pnpm install --frozen-lockfile
pnpm aplg:devkit:build
node packages/tauri-plugin-devkit/dist/cli.js --help
node packages/tauri-plugin-devkit/dist/cli.js init ./my-plugin --id io.github.example.demo --name "Demo"
node packages/tauri-plugin-devkit/dist/cli.js validate /path/to/plugin --json
node packages/tauri-plugin-devkit/dist/cli.js pack /path/to/plugin --out-dir ./release --json

根目录的 build/test 入口会先构建 runtime,确保 devkit 消费真实公共出口,而非源码 alias。直接在 devkit 目录运行前也需先完成 runtime build。

import { initProject, packProject, validateProject } from "@ai-switch/tauri-plugin-devkit";

const initialized = await initProject("./my-plugin", {
  id: "io.github.example.demo",
  name: "Demo",
  template: "vanilla-ts",
});
console.log(initialized.directory, initialized.files);

const report = await validateProject("./my-plugin", {
  stage: "source", // 默认 source
  profile: "web-v1", // 首期仅支持此 profile
});
if (report.valid) {
  console.log(report.manifest.id, report.manifestSha256, report.files);
} else {
  console.error(report.diagnostics);
}

// 先由作者显式 build;pack 不会运行 build/install/Vite config。
const packed = await packProject("./my-plugin", {
  outDir: "./release", // 相对路径按插件项目根解析
  profile: "web-v1",
});
console.log(packed.path, packed.sha256, packed.size);

包根入口是 Node-only ESM;导入本身不会读取项目、执行 CLI 或访问浏览器 DOM。公共报告类型直接复用 runtime 的 Manifest / Diagnostic。/vite 是独立 Node 构建插件入口,/testing 是浏览器安全的内存模拟宿主入口,/node-types 仅包含声明。内部 src/dist 路径不是公共接口。

D1 的 source 校验范围

只读以下五个固定文件,不遍历/导入项目源码、vite.config.*、node_modules 或 .env:

| 文件 | 上限 | 检查 | | --- | --- | --- | | aplg.json | 256 KiB | 严格 UTF-8/JSON、runtime manifest/schema/路径规则、API 范围、native=false | | package.json | 256 KiB | 严格 UTF-8/JSON、对象及 version 字符串、与 manifest 版本精确一致 | | README.md | 1 MiB | 普通文件、有效 UTF-8、非空 | | LICENSE | 1 MiB | 普通文件、有效 UTF-8、非空 | | pnpm-lock.yaml | 4 MiB | 普通文件、有效 UTF-8、非空 |

  • JSON 使用严格 AST 解析,拒绝注释、尾逗号、BOM、重复(含转义等价)键、不安全对象键、非有限数字、超过 64 层的嵌套;语法错误报告行/列,不回显任意输入片段。
  • profile 是校验选项,不向 runtime manifest 添加私有 profile 字段。web-v1 不接受 permissions.native:true;网络/文件权限的合法声明仍按 runtime 契约检查,不因此授予权限。
  • source 不要求 dist 已存在,也不证明入口源码、构建配置或依赖可运行。
  • 锁文件存在并非冻结安装已验证:D1 不解析其依赖图或执行 pnpm。作者须先真实安装、提交匹配的锁文件,再在 CI 中执行冻结安装。
  • 成功报告的 files 是本次已读元数据文件的相对路径、大小和 SHA-256,不是可直接打包的归档文件白名单;stage:"dist" 返回另行检查的产物候选列表,D5 再从这些候选创建独立私有快照并打包。manifest SHA 覆盖精确字节,包括空白和换行,不重新格式化后再算。
  • 不执行 package scripts、Vite config、build、安装依赖或网络请求。静态检查不等于恶意代码沙箱、签名验证、OS 权限授权或可发布承诺。

文件读取逐级 lstat,拒绝符号链接/junction/非普通文件;open 后复核 identity/size,并用至多 64 KiB 读取块,读后再次核查 identity、size、mtime/ctime 和路径组件。POSIX 使用可用的 no-follow/non-blocking 标志;这些检查减少并检测路径竞态,不宣称普通 Node fs 能提供原子目录级授权或对抗任意并发恶意文件系统。后续归档操作必须使用经验证的独立快照,不能把 D1 返回的 hash 当作未变化的文件句柄。

CLI 输出与退出码

aplg validate [directory] [--stage source|dist] [--json]
aplg inspect <file.aplg> [--json]
aplg pack [directory] [--out-dir <directory>] [--json]
aplg --help
aplg --version

--stage=source、--out-dir=./release、--id=...、--name=... 和 --template=vanilla-ts 都支持;-- 后只作为目录参数解析。默认项目目录为当前工作目录,pack 默认输出到项目根的 .aplg-output。重复选项、未知选项或多余位置参数直接失败,不猜测执行其他操作。

  • 0:成功。
  • 1:参数/内容/路径/兼容性校验失败。缺少项目、必需元数据、已有同名输出,或者请求尚未实现的命令也返回 1。
  • 2:OS I/O 或内部错误。程序化 API 对此抛出安全错误(如 code:E_IO),不返回“内容无效”的假诊断;CLI 不显示 native stack、真实绝对路径或原始 OS 错误文本。

指定 --json 时 stdout 只有一个 ProjectReport、PackageInspection 或 pack 成功对象;pack 的 path 只返回最终文件名,不回显绝对输出目录。stderr 可有简洁提示。报告中的 manifest 是作者提供的元数据,请勿将敏感信息写入清单。人类可读诊断会转义终端控制字符。

init 只渲染包内固定的 vanilla-ts 模板,目标必须不存在或为空,不安装依赖、不运行脚本、不创建 Git 仓库;首次生成项目不会写入虚假 lockfile。--json 成功结果只返回目标目录名和生成文件相对路径。--stage dist 检查已有 dist、源清单与构建记录;缺少记录或产物时明确失败,不隐式运行 build。pack 只消费已经通过该检查的产物,仍不解包、不隐式 build。后续 D7–D9 完成前不发布此开发切片。

D2 的只读归档检查

import { inspectPackage } from "@ai-switch/tauri-plugin-devkit";

const archive = await inspectPackage("./notes-0.1.0.aplg");
if (archive.valid) {
  console.log(archive.manifest.id, archive.sha256, archive.size, archive.files);
}
console.log(archive.signature); // 始终 "not-verified"

aplg inspect file.aplg --json 与程序化 API 复用同一个检查器。只读归档、不创建解包目录、不写出成员、不 import 或执行 JS。成功表示本节的结构/字节检查通过,不是签名可信、代码安全、已授权或可以直接安装。

支持的 web-v1 ZIP 子集

  • 文件扩展名必须精确为 .aplg。单盘经典 ZIP,stored/deflate 两种压缩方法;不接受 ZIP64、加密、data descriptor、自解压前缀、未引用记录/空洞、重叠内容、尾部附加内容。central 条目顺序可以不同于 local 顺序。
  • 自行读取 EOCD 与每个 central/local header,比较名字原始字节、版本、flags、method、CRC、尺寸、时间和 offset;再与 yauzl lazyEntries 的解析结果核对。允许无歧义 ASCII 或带 UTF-8 flag 的严格 UTF-8 名称;不隐式把反斜杠、CP437 或 Unicode extra-field 名字改写成另一条路径。
  • entry 的名字/extra/comment 元数据分别最多 4 KiB。extra field 仅允许标准 0x5455 时间戳(兼容 yazl);Unicode path override、ZIP64、NTFS/security/vendor extra 一律拒绝。DOS/Unix 创建器以外的权限编码不接受。
  • 路径复用 runtime 的 portable/NFC 规则,阻止盘符/UNC/设备名/..、尾随点空格、大小写碰撞、文件与目录冲突;进一步保守拒绝已知兼容字符、8.3 短名、控制/方向字符歧义。支持正常非 ASCII 名称;并不宣称穷尽所有文件系统名称等价规则。
  • 只允许 aplg.json、dist/**、LICENSE 和可选 README/THIRD_PARTY_NOTICES/icon.svg/icon.png。清单恰好一份,LICENSE 非空,声明 HTML entry 必须是已有文件;目录项使用一个尾随 /、stored、零尺寸/CRC,只计入条目数、不输出为 PackFile。
  • 拒绝符号链接、特殊文件/权限位、可执行权限的文件、native 目录、依赖/Git/隐藏元数据、常见私钥/源码/source-map/可执行及动态库文件名。流内额外识别 MZ/ELF/Mach-O 等常见原生头部与 PEM 私钥标记。该保守检查不是病毒或秘密扫描器,可能拒绝同样前缀的合法二进制;改名/静态检查不能证明任意内容无害。

流式预算与结果

压缩包最多 128 MiB、展开总量 512 MiB、条目 10,000,均引用 runtime limits。清单最多 256 KiB、HTML 2 MiB、JS/CSS 16 MiB;已知超限的 header 在读 body 前失败,实际解压数据也独立计数。高压缩比与伪造声明不能绕过计数;目录也占条目名额。

每次只检查一个成员:从只读 FileHandle 经有界 reader 提供压缩数据,stream/pipeline 解压、逐块 CRC-32/ISO-HDLC 与 SHA-256、检查长度和 deflate 消费字节数。除有界清单外不缓存整份成员。校验整个文件与每个实际字节文件的 hash;返回 files 仅含相对路径并稳定排序,读取前后核查文件和路径 identity/尺寸/mtime/ctime。更换/截断/改写会失败;普通文件检查仍不替代 D5 的独立可信快照和安装端原子操作。

成功、结构失败、解压失败或 I/O 错误都释放流和唯一自有句柄。结构/内容错误返回 valid:false, signature:"not-verified";OS I/O 仍为安全异常,CLI 退出 2,不泄漏真实路径或错误栈。

D2 不扫描 HTML/CSS/JS 的离线资源图或运行行为。例如含 Node import 的文本可以作为字节通过 inspect,但尚不能通过D3/D4 的构建/产物政策。正式打包 D5、签名/安装和商店审核是不同边界。已验证真实 yazl addBuffer 输出(有/无时间戳 extra),不是用被测 packProject 产生所有成功夹具;恶意 ZIP 由独立头部构造器生成。

D3 的插件 Vite 构建适配

只在插件自身的 vite.config.ts 中启用,不放进 AI Switch 主应用/宿主配置:

import { defineConfig } from "vite";
import { aplgVite } from "@ai-switch/tauri-plugin-devkit/vite";

export default defineConfig({
  plugins: aplgVite({ preview: false }),
});

插件代码可以使用 Node 风格 ESM 导入,但语义仍是 runtime 的受限浏览器接口:

import path from "node:path";
import { Buffer } from "buffer";
import fs from "node:fs/promises";

const file = path.resolve("notes.txt"); // 固定虚拟 cwd /data
const bytes = Buffer.from("hello", "utf8");
// 真正 I/O 仍要求宿主握手、manifest 声明和能力授权。
await fs.writeFile(file, bytes);

| 精确导入(均支持 node: 前缀) | runtime 目标 | | --- | --- | | fs/promises | /node/fs/promises | | fs | /node/fs | | path | /node/path | | buffer | /node/buffer(仅 named Buffer,无伪造 default) | | events | /node/events |

  • 不改写 path-helper 等相似包名,不替换源代码字符串,不安装全局 process/Buffer/require。静态字面量 CommonJS require 可交由 Vite 转换;动态/别名 require、未支持 builtin、.node 文件会收到 APLG 诊断。
  • 使用 AST/局部作用域识别未支持的 Node globals、已知全局属性/解构,区分注释、字符串、类型语法、普通对象属性和本地变量。依赖模块同样检查;runtime 已打包模块及精确的 Rolldown virtual runtime helper 不重复作为作者源码检查。
  • 拒绝 runtime /host、devkit/Tauri 管理入口进入插件图,包括可识别的安装路径/alias 绕行。最终 chunk import/dynamicImport 列表再检查,避免 external 选项绕过 resolver 留下真实 Node 导入。
  • 将 runtime 排除出 Vite dev dependency optimization,保留原有共享 ESM chunks/插件单例;测试覆盖 build 与 dev transform。SSR/server 环境不适配,Vite 配置脚本和 CLI 继续使用真正 Node API。没有配置 aplgVite 的宿主构建不受影响。
  • Node 文件 API 是异步客户端,不在网页制造真实磁盘;没有宿主时明确 E_HOST_UNAVAILABLE。

当前阶段边界: D4 已接入握手 bootstrap 和离线资源静态检查,manifestPath 支持根目录内的相对源清单;D7 增加显式 preview 选项和内存测试宿主。不要把静态诊断当作恶意代码沙箱:显式 Vite build 会运行作者配置/依赖,计算 URL、运行期生成代码、额外插件改写等仍需生产 CSP/权限系统约束。

浏览器 TypeScript 配置

仅给插件业务代码使用,Vite 配置单独使用 Node tsconfig:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "lib": ["ES2022", "DOM"],
    "types": ["@ai-switch/tauri-plugin-devkit/node-types"],
    "strict": true,
    "skipLibCheck": false,
    "noEmit": true
  },
  "include": ["src"]
}

/node-types 是 types-only 子入口,不能运行期 import。不要同时注入 @types/node:它会把 streams/watch/fd 等不支持的完整 Node API 带入编译上下文。声明直接 re-export runtime 公共类型,和 Vite alias 由 src/vite/node-specifiers.mjs 同表生成;generate:node-types 更新,构建和 check:node-types 检查陈旧产物。普通 Node 配置没有这些 ambient declarations,仍获得原生类型。

D3 验收在仓库外临时项目安装真实 runtime .tgz 与固定 Vite,生成匹配的 npm lock 后运行 npm ci --ignore-scripts。驱动构建的是 devkit 固定 Vite 8.3(避免把临时 Rolldown native DLL 装入 Windows 测试进程而锁住清理目录);不是 D8 的两包独立工具进程验收。公共类型测试另外把已构建的 devkit 声明按包结构复制到消费者,验证上述 types 配置。Chromium/WebKit 实际执行构建结果,测试 path、Buffer、EventEmitter、fs 单例及缺少宿主错误;不声称已接入真实 OS Provider。

D4 的握手启动与离线产物校验

普通 aplgVite({preview:false}) 现在同时启用 Node alias 和构建策略:

  1. 读取根目录 aplg.json,或 manifestPath 指定的项目内相对文件;以原始 UTF-8 字节计算 SHA-256,构建期间不允许清单变化。
  2. 只接受 manifest 指定的一份 HTML。最多一个本地外部 type="module" 业务脚本;拒绝 classic/inline/multiple/remote script、inline handler/style、importmap、base 和活动嵌入文档。纯静态零业务脚本 HTML 允许,也注入仅用于宿主会话握手的 bootstrap,不执行虚构业务代码。
  3. 用虚拟模块替换业务 script,不再并排添加启动器。先 await connectPlugin(),成功后才动态 import() 原业务入口;连接失败显示 Plugin connection failed.,业务异常显示 Plugin startup failed.,不复制任意错误内容、不以业务直跑作 fallback。
  4. 构建要求 base:"./"、outDir:"dist"、ES2022、ESM/动态分块,不允许 source map、library/multi-entry/自定义外部输出。config/buildStart/renderStart 重新核对输出路径与链接类型,避免配置变化使 Vite 清理/写出项目外目录。检查不意味着恶意 Vite 配置受沙箱限制。
  5. 禁用 Vite 的盲目 copyPublicDir;先检查 public/ 的普通文件、链接、路径、体积、秘密/不支持文件名,再显式 emit 为资产,与生成 chunks 一并检查/散列。公共资产与生成文件冲突直接失败,public 输入缓冲总量限制 128 MiB。
  6. 输出 dist/aplg-build.json:工具/格式版本、manifest 路径与 SHA、HTML entry、bootstrap/business chunk、排序的路径/大小/SHA 清单,不包含记录自身摘要以避免自引用。记录最多 1 MiB。

CLI/Node API 复用产物检查:

# 先显式运行构建;build 不是沙箱。
pnpm build
aplg validate . --stage dist --json
const report = await validateProject("./plugin", {stage:"dist"});

source 仍只读五个元数据文件;dist 追加收集白名单目录下的产物、匹配 manifest/record 精确 hash、核对单一启动 script 和动态 business 边,返回根清单/README/LICENSE 与 dist 文件(不将 package.json/lock 当归档输入)。manifestPath 只指定 Vite 的源清单;根目录的标准项目校验仍读取 aplg.json,其清单内容必须与记录的源清单一致。D5 的 packProject 只消费这份已有且有效的 dist 报告,不执行项目源码。

资源与行为边界

  • parse5 解析 HTML/SVG 的 src/href/srcset 等引用;PostCSS/value-parser 检查 CSS url/import/image-set;es-module-lexer 检查静态及可分析动态 import。Acorn 8.18.0 补充Node 校验侧语法、字面量 URL/Worker/require/comment 检查,普通 CLI 不因此强制依赖可选 Vite。
  • 拒绝远程/CDN/协议相对 URL、绝对系统路径、残留 bare/Node/host-only 导入、路径逃逸、缺失资源和 source maps;只允许有限 data:image 与本地 fragment。CSS 转义、HTML 实体和百分号编码必须不能改变路径边界。HTML 的相对 src 是 URL,不误判为 JS 的 bare package specifier。
  • manifest、HTML/JS/CSS 保留各自限额;目录/条目和展开大小沿用 runtime 10,000/512 MiB。二进制文件流式 hash,解析文本有界保留;这些是资源预算,不是一个可忽略服务级内存配额的在线上传解析器。
  • 非压缩构建中只删除经过语法解析定位的构建来源注释,不替换业务字符串;未删除的绝对 source-root 信息仍被检查拒绝。文件读取遵守既有 identity/尺寸/mtime/ctime 检查,不承诺抵抗任意恶意并发文件系统。
  • 构建记录未签名,不证明代码确实握手或无害。 只改文件会因 hash 不匹配失败;同时改脚本和 record 仍可能保持静态图自洽。生成器的握手行为有真实浏览器测试,但安装端必须独立建立签名、来源、CSP/IPC 与 OS 授权。fetch(computedUrl) 等动态网络行为不是静态资源引用的完备证明。

D4 浏览器验收在两个 loopback 来源与不允许 unsafe-inline/unsafe-eval 的 CSP 下运行真正构建产物。测试专用门控拦截 ready 后才释放给 runtime 公共 host,验证握手前无业务副作用、成功执行一次、连接/业务失败安全提示、无宿主不 fallback、零脚本和正常挂载/关闭。它不是 devkit /testing 的生产实现,也不表示 Tauri/真实 Rust 已接入。

D5 的可复现安全打包

packProject(root, options) 与 aplg pack 仅归档 aplg.json、非空 LICENSE、README.md、已有 dist/**,以及存在时的 THIRD_PARTY_NOTICES.md、icon.svg、icon.png。package.json、lock、.env、src/**、node_modules 和默认 .aplg-output 都不在候选中。

  • 每个候选逐级 lstat,拒绝 symlink/junction/非普通文件;open 后复核 identity、size、mtime/ctime,并以不超过 64 KiB 的块复制到带随机所有权 marker 的本次私有临时目录。复制结果必须与 D1/D4 报告的大小和 SHA-256 一致,ZIP 压缩阶段只读取私有快照。
  • ZIP 条目按包内路径的 UTF-8 字节顺序写入,统一 mode 0100644、DOS 时间 1980-01-01 00:00:00、压缩级别 9,不写 entry/archive comment 或扩展时间字段。锁定 yazl 版本和输入时保证本工具链内字节可复现,不承诺跨压缩器版本字节相同。
  • 默认输出 <root>/.aplg-output/<id>-<version>.aplg;相对 outDir 按项目根解析。拒绝 dist 内输出和任一 linked 输出目录。最终路径已存在时返回 E_OUTPUT_EXISTS,绝不覆盖。
  • 先在最终 outDir 以 exclusive create 写唯一 .staged.aplg,关闭后调用 inspectPackage 自检,并逐项比对快照 hash/size。随后只用同文件系统 hard-link 建立最终路径,平台不支持原子 no-clobber 时返回 E_OUTPUT_PUBLISH,禁止退化为覆盖式 rename。失败会按文件 identity 回滚本次 stage/已建立的 final link。
  • inspectPackage 自检仍只是结构与内容校验,signature 仍为 not-verified;它不代表签名信任、恶意代码沙箱、权限批准或商店审核。

D6 的无副作用 vanilla-ts 模板

initProject(directory, options) 和 aplg init 只使用 devkit 自带模板,不 import 或执行用户项目配置,不调用 npm/pnpm、git、gh 或任何安装/构建命令。

  • id 必须是小写命名空间标识,name 是非空、无控制字符且不超过 160 个 Unicode 字符的显示名;模板选择首期固定为 vanilla-ts。JSON 使用 JSON.stringify,HTML 使用实体转义,模板不会把名称拼接成路径或 shell 片段。
  • 目标必须是普通目录且为空,或由 init 安全创建;目标及祖先的 symlink/junction 会被拒绝。初始化先用独占 .aplg-init.lock 取得目录归属,再逐文件 exclusive create;并发调用只有一个成功。
  • 生成项目固定为 private: true,包含 aplg.json、Vite 配置、浏览器 src/main.ts/CSS、可独立测试的 src/example.js、Node 测试、浏览器/Node 分离的两个 tsconfig、README、LICENSE、.gitignore 和只读 GitHub CI。不会生成 node_modules、.git 或虚假 pnpm-lock.yaml。
  • 生成 README 指向 ai-switch/plugin-store 的公开 PR 流程,说明首次 pnpm install、固定源码 commit/manifest SHA 和权限边界;CI action 使用固定 commit,不含 pull_request_target、发布凭据、写权限或自动发布。
  • 写入失败只删除本次创建且 identity 未改变的文件/空目录;如果目标身份发生变化则停止清理,避免递归删除用户内容。

模板已随 npm tarball 的 templates/ 发布。由于包管理器可能忽略源模板目录中的隐藏文件,仓库中以 templates/vanilla-ts/gitignore 保存源文件,init 渲染时输出目标项目的 .gitignore。

D7 的显式内存测试宿主与开发预览

@ai-switch/tauri-plugin-devkit/testing 只提供浏览器安全的内存模拟宿主,不访问 Node fs、浏览器本机磁盘、管理 token 或生产宿主 API。它消费 runtime 公共 HostTransport 与协议校验;dispose() 会清空全部会话、存储和内存文件。

import { createTestHost } from "@ai-switch/tauri-plugin-devkit/testing";

const host = createTestHost({
  manifest,
  assetUrl: "http://127.0.0.1:43272/plugin/index.html",
  memoryFiles: { "/data/note.txt": new TextEncoder().encode("hello") },
});
  • aplg.storage 只有在 manifest 声明后才出现在会话能力中。
  • aplg.fs 只有在 manifest 声明且调用方显式传入 memoryFiles 时才出现;所有路径仍是 runtime 的虚拟路径,不映射真实 OS 文件。
  • 会话、订阅和文件 transfer handle 按 session/插件归属隔离;关闭 session 或 dispose 后旧句柄失效。disconnect()/reconnect() 只改变连接状态,不自动重放调用。

aplgVite({ preview: true }) 在 vite serve 时提供 /__aplg_preview__/ 开发壳。它只绑定 loopback、校验 Host 头、关闭 iframe HMR 和 Vite client 注入,并在沙箱 iframe(不授予 allow-same-origin)中通过真实 runtime 桥完成握手。页面明确显示“模拟宿主 / 内存数据”;需要文件能力时通过 memoryFiles 显式提供固定内存快照。生产 build 不包含 preview 路由、/testing mock 或内存数据。

本地浏览器验收覆盖 Chromium 和 WebKit:真实握手与业务启动、disconnect/reconnect、重新挂载产生新会话、无 memoryFiles 时明确 E_CAPABILITY_UNAVAILABLE、显式内存文件读写、非 loopback Host 拒绝,以及生产产物不含 preview/mock。

D8 的双 tarball 外部验收

pnpm --dir packages/tauri-plugin-devkit verify:tarball 会把 runtime 与 devkit 分别用 pnpm pack 生成真实 tarball,在 os.tmpdir() 的独立消费者中安装(不共享 workspace symlink),再用打包后的 CLI 执行 init → install → Vite build → validate --stage dist → pack → inspect。发布元数据断言确保 workspace: 协议不会进入 tarball。

根仓库的 scripts/aplg/verify-pair.mjs 接受两个已构建 tarball:

node scripts/aplg/verify-pair.mjs --runtime <runtime.tgz> --devkit <devkit.tgz>

它在外部消费者中保留 fixtures/aplg/plugin-example 的来源与 7 个上游行为测试,完成上述闭环后,解包经 inspect 校验的 .aplg,在受控 loopback host 中以真实 runtime createPluginHost 加载,并运行 Chromium/WebKit 的握手、文本统计行为、不透明 iframe 与父页面/存储隔离验收。inspect 仍是 not-verified,不表示签名、安装或商店审核已实现。

发布与 CI

.github/workflows/tauri-plugin-runtime.yml 在普通 PR/main push 上运行两包 typecheck/test/build/pack、verify-pair、plan-release 与 publish-npm 演练,不授予 npm 或 OIDC 写权限。

只有 tauri-plugin-runtime-v* tag 才进入 aplg-npm-release environment 的发布 job:先验证 tag 在默认分支上,再构建/打包/verify-pair,最后调用 scripts/aplg/publish-npm.mjs --execute。脚本默认 dry-run;真实执行使用 npm trusted publishing 与 provenance,按 runtime → devkit 顺序以 latest 发布。npm 的 OIDC 只认证 npm publish,因此不存在独立的候选 tag 与提升步骤;第二包失败时第一包已发布,重跑按 integrity 幂等跳过,不宣称事务性。使用者应使用锁文件或精确版本。

开发验证

从仓库根目录;首次浏览器测试需先安装 Chromium/WebKit:

pnpm --dir packages/tauri-plugin-devkit exec playwright install chromium webkit
pnpm aplg:devkit:test
pnpm --dir packages/tauri-plugin-devkit typecheck
pnpm --dir packages/tauri-plugin-devkit build
pnpm --dir packages/tauri-plugin-devkit check:node-types
pnpm --dir packages/tauri-plugin-devkit test:types
pnpm --dir packages/tauri-plugin-devkit test:types:browser
pnpm --dir packages/tauri-plugin-devkit test:built
pnpm --dir packages/tauri-plugin-devkit test:browser

test 会先构建 devkit,以便新 checkout 的公共类型消费者测试有真实 dist。测试包括真实临时目录、无副作用配置反例、部分读取/截断/改写、JSON 歧义、恶意 ZIP/解压预算/句柄回收、真实 yazl 输出、私有快照竞态、mtime/时区可复现性、并发 no-clobber/发布回滚、init 覆盖保护/转义/并发/失败清理、CLI stdout/exit codes、构建后 Node import/CLI 和公共类型。另有外部 tarball 消费测试实际运行生成模板的 test/typecheck/build/validate/pack。不修改开发者项目;测试临时目录在 finally 中复核路径/身份/所有权后删除。

vite:^8.3.0 为 optional peer,纯 CLI 不要求安装 Vite。其余计划依赖已按固定版本写入锁文件,D1 使用 runtime 与 jsonc-parser,D2 增加 yauzl 流式读取,D3 的 /vite 入口使用 Vite 8.3 的解析器;D4 的静态检查使用已声明的 HTML/CSS/module parser 与 Acorn。devkit 构建将 registry 依赖保持 external,不把 Node 工具代码混入 runtime 浏览器图。

对外打包应使用 pnpm pack 将 workspace:0.1.1 转换成真实 0.1.1;不要用 npm pack 直接分发带 workspace: 的源码 package.json。两包 0.1.1 已发布到 npm,并已用仓库外消费者完成 install/build/pack/browser 验收。

License

MIT。见 LICENSE 和 THIRD_PARTY_NOTICES.md。