Skip to content

隐式包执行

执行全局安装包或按需运行包,无需显式安装,类似于 npxuvx,但支持跨语言。

概述

隐式包执行功能允许你使用统一语法直接运行包。与 npx(仅 Node.js)或 uvx(仅 Python)不同,vx 支持多个生态系统,提供一致的界面。

主要优势:

  • 🚀 一键执行:无需预先安装即可运行包
  • 🌍 跨语言:适用于 npm、pip、cargo、go 等
  • 📦 自动安装:首次运行时自动安装包
  • 🔒 隔离性:每个包都安装在自己的隔离环境中
  • 🎯 版本控制:指定确切版本以确保可重现性

语法

vx <生态系统>[@运行时版本]:<包名>[@版本][::可执行文件] [参数...]

语法组件

组件描述示例
生态系统包生态系统(npm、pip、cargo、go 等)npmpip
@运行时版本(可选)要使用的运行时版本@20@3.11
包名包名称typescriptruff
@版本(可选)包版本@5.3@0.3
::可执行文件(可选)可执行文件名(如果与包名不同)::tsc::rg

基本用法

运行已安装的工具

通过 vx global install 安装包后,可以直接运行:

bash
# 通过可执行文件名运行已安装工具
vx tsc --version
vx black --check .
vx rg "pattern" ./src

显式包语法

当包名与可执行文件名不同时,使用完整语法:

bash
# 包名 ≠ 可执行文件名
vx npm:typescript::tsc --version      # typescript 包,tsc 可执行文件
vx pip:httpie::http GET example.com   # httpie 包,http 命令
vx cargo:ripgrep::rg "pattern"        # ripgrep 包,rg 可执行文件

自动检测与安装

如果包尚未安装,vx 会自动下载并安装:

bash
# 首次运行 - 自动安装 typescript
vx npm:typescript --version

# 首次运行 - 自动安装 ruff
vx pip:ruff check .

# 包会被缓存以供后续使用

支持的生态系统

生态系统别名运行时示例包
npmnodeNode.jsnpm:typescript
pippythonpypiPythonpip:black
uv-Python(通过 uv)uv:ruff
cargorustcratesRustcargo:ripgrep
gogolangGogo:golangci-lint
bun-Bunbun:typescript
yarn-Node.jsyarn:typescript
pnpm-Node.jspnpm:typescript

常见用例

TypeScript/Node.js

bash
# 编译 TypeScript(如需要自动安装)
vx npm:typescript::tsc --version

# 运行 ESLint
vx npm:eslint .

# 使用指定 Node 版本创建 React 应用
vx npm@18:create-react-app my-app

# 运行作用域包(@biomejs/biome)
vx npm:@biomejs/biome::biome check .

# 运行指定版本的 TypeScript
vx npm:typescript@5.3::tsc --version

Python

bash
# 使用 black 格式化代码
vx pip:black .

# 使用 ruff 检查(指定版本)
vx pip:ruff@0.3 check .

# 运行 pytest
vx pip:pytest -v

# 使用指定 Python 版本
vx pip@3.11:black .

# 使用 uv(更快)
vx uv:ruff check .

# HTTP 客户端
vx pip:httpie::http GET example.com

Rust

bash
# 使用 ripgrep 搜索
vx cargo:ripgrep::rg "pattern" ./src

# 使用 fd 查找文件
vx cargo:fd-find::fd ".rs$" .

# 使用 bat 语法高亮
vx cargo:bat::bat src/main.rs

Go

bash
# 运行 linter
vx go:golangci-lint run

# 运行语言服务器
vx go:gopls version

:: 分隔符说明

许多包提供的可执行文件名与包名不同。:: 分隔符让你可以指定确切的可执行文件:

包名可执行文件完整命令简写(如已安装)
typescripttscvx npm:typescript::tscvx tsc
typescripttsservervx npm:typescript::tsservervx tsserver
httpiehttpvx pip:httpie::httpvx http
ripgreprgvx cargo:ripgrep::rgvx rg
fd-findfdvx cargo:fd-find::fdvx fd
batbatvx cargo:bat::batvx bat

何时使用 ::

使用 :: 的情况:

  • 包名与可执行文件名不同(如 typescripttsc
  • 包有多个可执行文件(如 typescripttsctsserver
  • 你想明确指定运行哪个可执行文件

省略 :: 的情况:

  • 包名等于可执行文件名(如 eslintruff
  • 安装后通过简写运行

版本规范

包版本

bash
# 最新版本(默认)
vx npm:typescript --version

# 指定版本
vx npm:typescript@5.3 --version

# 版本范围
vx npm:typescript@^5.0 --version

运行时版本

bash
# 使用指定 Node.js 版本
vx npm@20:typescript::tsc --version
vx npm@18:eslint .

# 使用指定 Python 版本
vx pip@3.11:black .
vx pip@3.12:ruff check .

# 使用最新运行时(默认)
vx npm:typescript --version

组合规范

bash
# 完整规范:生态系统@运行时:包名@版本::可执行文件
vx npm@20:typescript@5.3::tsc --version
# │    │  │          │   │  │
# │    │  │          │   │  └── 可执行文件
# │    │  │          │   └───── 包版本
# │    │  │          └───────── 包名
# │    │  └──────────────────── 运行时版本
# │    └─────────────────────── 运行时
# └──────────────────────────── 生态系统

作用域 npm 包

对于带作用域的 npm 包(@组织/包):

bash
# Biome(JavaScript 工具链)
vx npm:@biomejs/biome::biome check .

# OpenAI Codex
vx npm:@openai/codex::codex

# TypeScript Go 实现
vx npm:@aspect-build/tsgo::tsgo check .

与现有工具对比

vx vs npx

场景npxvx
基本执行npx eslintvx npm:eslintvx eslint(已安装)
不同可执行文件npx -p typescript tscvx npm:typescript::tsc
指定版本npx eslint@8vx npm:eslint@8
运行时版本❌ 不支持vx npm@20:eslint
其他生态系统❌ 不支持✅ pip、cargo、go 等

vx vs uvx

场景uvxvx
基本执行uvx ruffvx pip:ruffvx ruff(已安装)
不同可执行文件uvx --from httpie httpvx pip:httpie::http
指定版本uvx ruff@0.3vx pip:ruff@0.3
运行时版本uvx --python 3.11 ruffvx pip@3.11:ruff
其他生态系统❌ 不支持✅ npm、cargo、go 等

项目级配置

对于项目,可以在 vx.toml 中声明常用包:

toml
[tools.global]
typescript = "5.3"
eslint = "8"
black = "24.1"
ruff = "0.3"

然后直接使用:

bash
vx sync    # 安装所有声明的全局工具

vx tsc --version    # 使用项目的 typescript 版本
vx eslint .
vx black .

环境变量

变量描述
VX_AUTO_INSTALL启用/禁用自动安装(默认:true
VX_GLOBAL_CACHE覆盖全局包缓存目录

故障排除

"找不到包"

bash
# 确保使用正确的生态系统
vx npm:my-package      # 用于 npm 包
vx pip:my-package      # 用于 Python 包

# 检查包是否存在
vx global list

"运行时未安装"

bash
# 首先安装所需的运行时
vx install node        # 用于 npm 包
vx install python      # 用于 pip 包
vx install rust        # 用于 cargo 包

命令冲突

如果命令与运行时名称冲突:

bash
# 使用显式语法
vx npm:node             # 运行 'node' 包,而非 node 运行时

# 或使用全局命令
vx global install npm:node
vx node                 # 现在运行的是该包

最佳实践

1. 固定版本以确保可重现性

bash
# 好:指定版本
vx npm:typescript@5.3.3 --version

# 不太可预测:最新版本
vx npm:typescript --version

2. 在脚本中使用显式语法

bash
# 在 CI/CD 或共享脚本中,保持明确
vx npm:typescript@5.3::tsc --project tsconfig.json

3. 对于频繁使用的工具,优先使用 vx global install

bash
# 一次安装,多次使用
vx global install npm:typescript@5.3

# 然后使用简写
vx tsc --version

4. 使用 vx dev 进行项目隔离

bash
# 进入项目环境
vx dev

# 所有工具都可用,无需前缀
tsc --version
black .
ruff check .

实现细节

vx-shim Crate

vx-shim crate 实现了 RFC 0027 的解析和执行逻辑:

rust
// 解析 RFC 0027 语法
let request = PackageRequest::parse("npm@20:typescript@5.3::tsc")?;
// request.ecosystem = "npm"
// request.package = "typescript"
// request.version = Some("5.3")
// request.executable = Some("tsc")
// request.runtime_spec = Some(RuntimeSpec { runtime: "node", version: "20" })

架构:

┌─────────────────────────────────────────────────────────────────────────┐
│                         vx-shim 架构                                    │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │  PackageRequest(包请求)                                        │   │
│  │  ├── parse(input: &str) -> Result<Self>                         │   │
│  │  ├── is_package_request(input: &str) -> bool                    │   │
│  │  └── executable_name() -> &str                                  │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │  ShimExecutor(Shim 执行器)                                     │   │
│  │  ├── execute_request(req, args) -> Result<ExitCode>             │   │
│  │  ├── find_package(ecosystem, package) -> Option<GlobalPackage>  │   │
│  │  └── resolve_executable(package, exe_name) -> PathBuf           │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │  执行流程                                                        │   │
│  │  1. 解析请求 (ecosystem:package@version::executable)             │   │
│  │  2. 检查包是否已安装在 PackageRegistry 中                        │   │
│  │  3. 如未安装:返回 PackageNotInstalled 错误                      │   │
│  │  4. 如已安装:解析可执行文件路径                                 │   │
│  │  5. 使用运行时环境执行                                           │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

自动安装机制

当找不到包时,CLI 触发自动安装:

rust
// 在 vx-cli/src/lib.rs 中
async fn execute_package_request(ctx, spec, args) {
    match executor.execute_request(&pkg_request, args).await {
        Ok(exit_code) => Ok(()),
        Err(ShimError::PackageNotInstalled { ecosystem, package }) => {
            // 自动安装包
            auto_install_package(ctx, &pkg_request).await?;
            // 重试执行
            executor.execute_request(&pkg_request, args).await
        }
    }
}

这提供了类似 uvx/npx 的无缝体验:

  • 首次运行:自动安装并执行
  • 后续运行:从缓存执行

支持的语法模式

模式示例描述
简单npm:typescript包名 = 可执行文件名
带版本npm:typescript@5.3指定包版本
不同可执行文件npm:typescript::tsc显式可执行文件名
完整语法npm@20:typescript@5.3::tsc运行时 + 包版本 + 可执行文件
作用域 npmnpm:@biomejs/biome::biome作用域包与可执行文件
运行时版本pip@3.11:black指定运行时版本

解析器实现

解析器处理带作用域的 npm 包等边界情况:

rust
// 作用域包:@org/package@version
if part.starts_with('@') {
    // 处理 @types/node 或 @types/node@1.0
    if let Some(slash_pos) = part.find('/') {
        // 解析作用域和包名
        // 处理包名后的版本
    }
}

相关命令

基于 MIT 许可证发布