RFC 0042: 按平台生成的命令 shim(Platform Command Shims)
状态: Draft 作者: vx team 创建日期: 2026-10-06 目标版本: v0.10.0 关联: RFC-0025(Cross-Language Global Package Isolation)
摘要
引入 vx shim 命令族,把任意 Runtime 暴露成一个可以直接输入的一级命令。
vx shim add jq 会在 PATH 上的目录里按平台生成包装脚本,之后 jq --version 等价于 vx jq --version。生成的脚本与用户手工编写的内容一致:
#!/bin/sh
# Generated by vx-shim. Do not edit; managed by 'vx shim'.
exec "/path/to/vx" jq "$@"一个名字在 Windows 上生成两个文件(.cmd 覆盖 cmd.exe / PowerShell,无扩展名 脚本覆盖 Git Bash / MSYS2),在 Unix 上只生成一个 #!/bin/sh 脚本。
动机
现状痛点
用户已经在手写这个包装脚本。 典型做法是在
~/.local/bin/jq里放一段exec vx jq "$@"。这段脚本要自己写、自己 chmod、自己记得它是哪来的, vx 升级后也不会跟着更新。手写版本只覆盖一个平台、一个 shell。 上面的脚本在 Windows 的 Git Bash 里能用,但在 cmd.exe 和 PowerShell 里完全不可见——而
Warp插件、npm scripts、pre-commit钩子可能从任意一种 shell 里调用它。已有 shim 机制服务的是全局包,不是 Runtime。
vx-paths::shims只能把 shim 指向一个已存在的可执行文件,无法表达 "转发到vx <runtime>并按需安装"。vx-runtime::Shim支持前缀参数, 但没有任何调用方,也没有簿记——生成完就失联,无法列出、刷新或安全删除。同名冲突没有护栏。
git是最自然的 shim 目标,也是最危险的: 一旦覆盖系统git,所有版本控制操作都走 vx。手工脚本没有这层判断。
设计目标
vx shim add jq之后,jq --version直接可用(安装由 vx 按需完成)。- 单次 add 覆盖当前平台的全部 shell 形态。
- 能列出、刷新(
vx shim sync)、删除 vx 自己创建的 shim。 - 永不删除或覆盖不是 vx 创建的文件。
- 覆盖已存在的同名命令(如系统
git)必须显式--force。
非目标
- 不修改用户 PATH、shell rc 或 Windows 注册表。目录不在 PATH 上时只给出提示。
- 不提供静态二进制 shim(
shimexe-core是vx-paths::shims既有的 Future Enhancement,与本 RFC 独立)。 - 不在
provider.star新增"也暴露为 X"字段——那是声明式路由,与本机制的 命令式管理是两条路径,可以后续单独提案。
设计方案
1. 生成:复用并扩展 vx-runtime::Shim
Shim 已经支持 with_args,天然能表达"转发到 vx jq"。本次新增:
impl ShimType {
/// 一个平台需要生成的全部 shim 形态
pub fn platform_variants(platform: &Platform) -> Vec<ShimType>;
}
impl Shim {
pub fn create_all(&self, dir: &Path, platform: &Platform) -> Result<Vec<PathBuf>>;
pub fn remove_all(&self, dir: &Path, platform: &Platform) -> Result<Vec<PathBuf>>;
pub fn content_for(&self, shim_type: ShimType) -> String;
pub fn is_managed(path: &Path) -> bool;
}| 平台 | 生成的文件 | 覆盖的调用方 |
|---|---|---|
| Windows | <name>.cmd | cmd.exe、PowerShell(经 PATHEXT 解析 jq → jq.cmd) |
| Windows | <name> | Git Bash、MSYS2、Cygwin |
| Unix / macOS | <name> | 所有 POSIX shell |
三处平台相关的生成细节:
- Batch 显式传播退出码:追加
exit /b %ERRORLEVEL%,否则调用方拿到的 退出码会被setlocal边界吞掉。 - Shell 脚本中的 Windows 路径统一为正斜杠:
Path::display()在 Windows 输出反斜杠,POSIX shell 会把它当转义符。 - 每个文件都带
vx-shim标记行(VX_SHIM_MARKER),这是安全删除的依据。
2. 簿记:ShimRegistry
新增 crates/vx-runtime/src/shim_registry.rs,持久化到 $VX_HOME/config/command-shims.json:
{
"shims": [
{
"name": "jq",
"runtime": "jq",
"launcher": "C:\\Users\\me\\.cargo\\bin\\vx.exe",
"dirs": ["C:\\Users\\me\\.vx\\bin"],
"files": ["...\\jq.cmd", "...\\jq"],
"created_at": "2026-10-06T08:53:18Z"
}
]
}两个作用:
- 刷新:脚本里烘焙的是
vx的绝对路径。vx升级或换位置后,vx shim sync按 registry 重写全部 shim。 - 安全:
remove只删 registry 记录过、且内容带标记的文件。
3. 目标目录:沿用 stacked 布局
与 vx global install 一致(RFC 0025 的 collect_stacked_shim_dirs):
$VX_HOME/bin(vx托管的 bin 目录,在 vx 环境中已在 PATH 上)vx可执行文件所在目录(唯一确定已在 PATH 上的目录)
--dir 可覆盖,支持重复,用于 ~/.local/bin 这类用户既有约定。
不写入 $VX_HOME/shims:vx global shim-update 会把该目录下不在 包 registry 里的条目当陈旧 shim 删除,命令 shim 放进去会被误删。
4. 命令表面
vx shim add <runtime> [--as <name>] [--dir <dir>] [--force]
vx shim list [--json]
vx shim remove <name> [--force]
vx shim sync
vx shim pathadd:runtime 规范支持带版本(git@2.53.0);--as改名;未带--force时若名字已解析到非 vx 创建的二进制则直接报错。path:打印目标目录及其 PATH 状态,提示补 PATH 的确切命令(Windows 给 PowerShell 写法,Unix 给export写法)。list --json:面向脚本和 AI agent。
5. 冲突护栏
$ vx shim add git
✗ 'git' already resolves to C:\Program Files\Git\mingw64\bin\git.exe on PATH.
Re-run with --force to shadow it, or pick another name with --as.判定顺序:which <name> 命中 → 内容带 vx-shim 标记则放行(是自己的旧 shim,可覆盖)→ 否则未带 --force 即报错。
兼容性
- 纯新增命令与模块;
vx-runtime::Shim的既有 API 语义不变。 Shim生成内容新增标记行与exit /b,脚本行为不变(退出码更准确)。- 用户手写的
~/.local/bin/jq不会被自动接管;用户可以--force覆盖,或 先rm再用vx shim add jq --dir ~/.local/bin接管。
测试
crates/vx-runtime/tests/shim_tests.rs:平台形态矩阵、三套生成器内容、 标记与仅删除自有文件、registry 读写与容错(缺失/损坏文件)。crates/vx-cli/tests/shim_command_tests.rs:驱动真实vx二进制 + 临时VX_HOME,覆盖 add / list / remove / sync / path 与无--force时的覆盖拦截。
后续(不在本 RFC 范围)
provider.star声明式shims = ["jq"],vx install时自动创建。vx shim add时对 runtime 是否存在做校验(当前不校验,首次调用才安装)。