Skip to content

UniPty v0.2.0:命令输入更安全

jixoai v0.2.0 unipty

UniPty v0.2.0 新增两个可选的官方解析器包:@unipty/shell-parser 把 Bash 命令文本分类成结构化 argv 或显式的 shell 脚本请求,@unipty/powershell-parser 只认官方 Parser.ParseInput。分类全程不执行任何东西。一次发布的公共包从 6 个扩大到 8 个。

$ npm view @unipty/shell-parser version
0.2.0

UniPty v0.2.0 发布了(GitHub Release 于 2026-08-20 切出)。这一版落地 v1 架构预留的解析器生态:@unipty/shell-parser 与 @unipty/powershell-parser 两个可选的官方包接收命令文本,把它分类成可直接启动的结构化 argv,或携带 shell 语义、需要你显式接受的脚本请求。分类全程不执行任何东西。一次发布的公共包从 6 个扩大到 8 个。

背景:一份契约,三个运行时

Node、Bun 与 Deno 各自暴露互不兼容的 PTY 底层:安装模型、I/O 表示、生命周期语义、原生部署约束全都不同(这是 v1 架构提案开篇的原始动机)。UniPty 把这些差异吸收到一份公共契约后面:Core 拥有一切可观察行为,开发者显式选择 Backend,支持声明由证据门控。三条官方路线分别是 Node 走第三方 node-pty、Bun 走运行时原生的 Bun.Terminal、Deno 走自包含的 @sigma/pty-ffi。

Core 只接受结构化 argv,没有字符串命令重载。从字符串命令工具迁移过来的项目,此前要自己写「文本 → argv」的翻译;写错一次,隐式 shell 就是一次事故。

机制:支持声明如何被证据门控

一个 runtime/platform 组合只有在公共 conformance 套件对已安装的包产物(pack 并装入隔离消费者,只经公共导出驱动)全量通过时才标记 verified;其余一律是 declared-unverified 或 not-targeted。失败留在 CI 诊断里,不固化成永久性的 unsupported 声明(见 conformance 文档)。解析器包不进这张矩阵,验证走各包自带的语料单测。

Bash 命令文本不再需要手写翻译

@unipty/shell-parser 读一段命令文本,返回五分类之一,告诉你它能不能直接启动:

import { parse } from "@unipty/shell-parser";

parse("git status --force");
// → { kind: "argv", argv: ["git", "status", "--force"] }

parse("ls *.txt | wc -l");
// → { kind: "script", language: "bash", source: "ls *.txt | wc -l" }

parse("echo 'unterminated");
// → { kind: "incomplete", diagnostics: [...] }

五分类 argv | script | incomplete | unsupported | invalid 是这个包唯一承诺的稳定边界。argv 表示文本在词法上恰好是一条简单命令(引号与空参数保留),可以直接交给 unipty.spawn(argv)。script 表示文本携带 shell 语义(管道、重定向、展开、glob 等),启动的前提是你显式接受指定的 shell 策略。incomplete 是文本还没写完,invalid 是语法错误,unsupported 是 walker 无法判定的词构造,绝不猜测。

可执行文件怎么解析(PATH、内建、函数、别名)始终是你的决定,解析器不对命令名主张进程启动等价性。

分类政策刻意保守:转义元字符(echo \*)与未加引号的方括号字符([ -f x ])同样归入 script,有含糊就停在 shell 请求一侧。

这个包是对 unbash(精确锁版 4.0.10)的同步薄包装,唯一公共面是分类结果,unbash 的 AST 不外泄。纯 JavaScript、零平台 API,与 unipty 没有运行时依赖。见包文档。

机制:walker 如何证明「字面性」

argv 的判定来自 walker 对原始词文本的逐步证明:每个词在静态引号处理后必须是字面量,任何需要 shell 求值的构造(展开、替换、glob、重定向等)都会把文本推向 script。仅注释或带 shebang 的文本同样归入 script;诊断携带 UTF-16 源码区间,按「输入末尾未闭合」类消息区分 incomplete 与 invalid。

PowerShell 文本由官方解析器判定

PowerShell 的语法不由 JavaScript 去猜。@unipty/powershell-parser 的唯一语义权威是 PowerShell 官方的 Parser.ParseInput,由你指定的宿主执行,默认 pwsh:

import { parsePowershell } from "@unipty/powershell-parser";

await parsePowershell('dotnet build -c "My Config"');
// → { kind: "argv", argv: ["dotnet", "build", "-c", "My Config"] }

你的文本以 base64 编码的 UTF-8 走 stdin(静态适配脚本经 -EncodedCommand 传入),不拼接进命令行,也不受 Windows 控制台代码页歧义影响。

失败是类型化的三段,没有静默:宿主缺席 → capability-unavailable,绝不回落到 Bash 式解析;宿主启动但异常退出或返回未知结果 → host-failure,绝不降级成 script;超出预算(默认 15 秒,timeoutMs 可配)→ host-timeout。

宿主的选择是显式的:isPowershellHostAvailable() 先探,options.host 可以指定其它可执行文件,例如 Windows PowerShell 5.1 的 powershell(同样提供 Parser.ParseInput)。见包文档。

细节:诊断序列化与宿主探测

官方 ParseError 记录序列化为 { message, errorId, incomplete, range },带 UTF-16 偏移;标记为 IncompleteInput 的错误映射为 incomplete,其余归 invalid。

import { isPowershellHostAvailable, PowershellParseError } from "@unipty/powershell-parser";

await isPowershellHostAvailable(); // → 无 pwsh 时为 false

await parsePowershell("x", { host: "pwsh-preview" }); // 显式选择宿主

其余变更

  • 发布顺序先发两个 parser 包,再发核心与 Backend:新包名最可能没配好 Trusted Publisher,鉴权失败要发生在任何包已发布之前,npm 上不留半发布的版本(release workflow)。npm 时间戳可以核对:@unipty/shell-parser@0.2.0 于 11:00:13 上架,@unipty/powershell-parser@0.2.0 于 11:00:20,unipty@0.2.0 于 11:00:26 跟进,GitHub Release v0.2.0 于 11:01 切出。

  • shell-parser 把 locale 字符串($"...")与含 NUL 的 ANSI-C 词挡在 argv 之外(5bcd7d2)

  • powershell-parser 文本传输改走 stdin 并强制严格宿主失败(7f121ff)

  • 文档站消费真实的 release-catalog schema,并要求 aggregator(聚合校验)套件的通过戳(586e24c、e489776)

完整清单见 compare v0.1.1...v0.2.0。本版本无破坏性变更。0.1.1 之后 12 个提交:2 feat / 4 fix / 3 docs / 2 chore / 1 ci(口径为 conventional 提交前缀计数)。

升级

无破坏性变更。解析器生态是纯新增包,Core / Backend / acquisition / helper / conformance 代码零改动(变更提案的 Impact 原文)。升级即:

npm i unipty@0.2.0
# 可选:按需加装解析器
npm i @unipty/shell-parser@0.2.0 @unipty/powershell-parser@0.2.0

致谢

@unipty/shell-parser 基于 unbash 构建;三条 Backend 路线分别感谢 node-pty(经 @lydell/node-pty 预构建分发)、Bun.Terminal 与 @sigma/pty-ffi。

链接