安装

需要 Node.js ≥ 18.18(execa 的真实下限),以及至少装一个包管理器。

从 GitHub 安装(开发)
git clone https://github.com/hanqindylan/x-pm.git
cd x-pm
npm install
npm run build
npm link
全局安装 / 分享给别人
npm i -g github:hanqindylan/x-pm

装完会有两个命令:x 和 xgo。卸载用 npm unlink -g x-pm。

注意
顺序别反:先 npm run build 再 npm link。 dist/ 在 .gitignore 里,不会进仓库,别人 clone 后要自己构建。

命令总览

命令作用
x i安装依赖
x a <pkg>添加依赖,-D 走 devDependencies
x r <pkg>移除依赖
x up更新依赖
x ls列出顶层依赖
x run <script> / x <script>运行 package.json 脚本
x exec <cmd>执行本地可执行文件
x search <kw>远程搜索包(默认国内源)
x find <kw>本地搜索已安装的包
x where <pkg>精确定位包目录和可执行文件
xgo <cmd>统一 npx / pnpm dlx / bun x / yarn dlx
x use <pm>写入 packageManager 字段并切换

别名:i=install、a=add、r=remove、 up=update、ls=list、s=search、f=find。 还可以用 .xrc.json 的 aliases 加自己的。

全局选项(任何命令都能带):

--pm <pm>强制指定 npm / pnpm / bun / yarn,跳过检测
-h, --help查看帮助
-v, --version查看版本号
透传
x 只摘走自己的选项,剩下的原样转交。 x dev --port 3000、x i --legacy-peer-deps、 x a axios -- --registry 内网源 都不会丢参数。

包管理

x i [args...] 安装依赖
bash
$ x i
$ x i --frozen-lockfile

别名 install / in。对应 <pm> install。

x a <pkg...> 添加依赖
bash
$ x a axios lodash      # 一次加多个
$ x a -D typescript    # 开发依赖
$ x a axios --save-exact  # 原样透传
-D, --save-dev写入 devDependencies

选项会被自动排到包名之前,所以 x a axios -D 也会变成规范的 -D axios。

x r <pkg...> 移除依赖
bash
$ x r lodash

别名 remove / rm / uninstall。

x up [args...] 更新依赖

对应 npm / pnpm / bun 的 update;yarn 用的是 upgrade,x 会自动翻译,不用你记。

x ls [args...] 列出顶层依赖

对应 <pm> ls --depth=0,只列出直接依赖,不展开依赖树。

x run <script> [args...] 运行 package.json 脚本
bash
$ x run dev
$ x dev --port 3000   # 简写,参数透传

简写规则:任何没被识别成 x 命令的词,都当作 package.json 脚本去跑。 所以 x dev ≡ x run dev。

x exec <cmd> [args...] 执行本地可执行文件
bash
$ x exec eslint .
--sudo显式用 sudo 执行

和 xgo 的区别:exec 只走包管理器自带的 exec,强制使用项目本地环境,不会去 PATH 或 dlx 找。

x use <pm> 写入 packageManager 字段并切换
bash
$ x use pnpm
✔ 已切换到 pnpm
  packageManager: (无) → pnpm@10.14.0

! 目录里还残留其他包管理器的锁文件:package-lock.json
提示: packageManager 字段优先级最高,不删也不影响使用

x 会写完整版本号(通过执行 <pm> --version 获取);拿不到时只写包名。 发现其它锁文件时只提示不删除——删文件这种事不替你做主。

搜索与查找

x search <keyword> 远程搜索包,默认国内源
bash
$ x search react
源: registry.npmmirror.com    当前项目: npm

┌───────────┬────────┬────────────────────────────┬──────────────┐
│ 包名      │ 版本   │ 描述                       │ 发布者       │
├───────────┼────────┼────────────────────────────┼──────────────┤
│ react     │ 19.3.1 │ React is a JavaScript li…  │ GitHub Actions│
└───────────┴────────┴────────────────────────────┴──────────────┘

安装:x a react
--source <npm|cn>切官方源 / 显式国内源,也能直接传一个 URL
--limit <n>返回条数,默认 10
--where附加显示本地 / 全局路径
--json输出 JSON,方便脚本消费

底层接口是 GET {registry}/-/v1/search?text=…&size=…, npmmirror 和官方源格式一致。

x find [keyword] 在本地查找已安装的包
bash
$ x find eslint --local

┌────────────┬────────┬──────┬────────────┬──────────────────────────┐
│ 包名       │ 版本   │ 位置 │ 类型       │ 路径                     │
├────────────┼────────┼──────┼────────────┼──────────────────────────┤
│ eslint     │ 9.17.0 │ 本地 │ dependency │ ~/proj/node_modules/…    │
└────────────┴────────┴──────┴────────────┴──────────────────────────┘

共 1 个包
--local只查本地
--global只查全局
--bin只列出带可执行文件的包
--deep递归扫描,大仓库会慢
--jsonJSON 输出

扫描范围:当前项目 node_modules、monorepo workspace 里的 node_modules、以及全局目录。

性能
默认只扫顶层和 @scope 一层,不递归,大仓库也是毫秒级。 结果按 node_modules 的 mtime 缓存——你装了新包,缓存自动失效。 只有 --deep 才会递归进入每个包自己的 node_modules。
x where <pkg> 精确定位包目录和可执行文件
bash
$ x where eslint

eslint  (来源: pnpm-lock.yaml)

├─ 包目录
  本地:   /repo/node_modules/eslint
│
├─ 可执行文件
  本地:   /repo/node_modules/.bin/eslint
  PATH:   /usr/local/bin/eslint
│
└─ 元信息
  版本:   9.17.0
  来源:   pnpm
  主页:   https://eslint.org
--bin只输出可执行文件路径,每行一个,便于管道
--local只看本地
--global只看全局
接进管道
$ x where eslint --bin | head -1
/repo/node_modules/.bin/eslint

跨平台:内部封装 which,Windows 自动处理 .cmd / .exe 后缀和 PATHEXT。

xgo 统一 npx

xgo 顶替 npx / pnpm dlx / yarn dlx / bun x。x go <cmd> 是等价的写法。

bash
$ xgo cowsay hi
提示: 本地与 PATH 都没有 cowsay,使用 pnpm dlx 执行
$ xgo --pm pnpm vite --host

执行顺序

1本地 node_modules/.bin/<cmd>存在就直接用本地的,不下载
2PATH 环境变量找得到就直接执行
3<pm> dlx都没有才让包管理器去下载
--sudo显式用 sudo 执行
--pm <pm>强制指定用哪个包管理器的 runner

sudo 规则

x 永远不会自动加 sudo。只有两条路径会提权:

  1. 你显式传了 --sudo
  2. 执行失败、报错里有 EACCES / EPERM / permission denied,并且你在提示里回答了 y
安全
非交互环境(CI、管道、重定向)会直接跳过提问, 不会挂住等输入。Windows 没有 sudo,会提示你用管理员权限手动重试。

配置 .xrc.json

放在项目根目录,也可以放在上层目录——x 会用 find-up 向上找。

.xrc.json
{
  "registry": "https://registry.npmmirror.com",
  "defaultPM": "pnpm",
  "searchLimit": 10,
  "aliases": {
    "s": "search",
    "f": "find"
  }
}
字段说明
registry搜索用的源地址
defaultPM锁文件缺失时的兜底包管理器
searchLimitx search 默认返回条数
aliases自定义命令别名
容错
配置文件里写错的字段会被忽略并给一条中文警告, 而不是让整个命令崩掉——配置文件手滑不该导致工具不可用。

包管理器检测规则

从高到低逐级判断,命中即停:

1--pm <pm>命令行显式指定
2package.json#packageManager命中后忽略所有锁文件
3package-lock.json→ npm
4pnpm-lock.yaml→ pnpm
5bun.lockb / bun.lock→ bun
6yarn.lock→ yarn
7.xrc.json#defaultPM锁文件缺失时的兜底
8都没有→ 默认 npm

支持 packageManager 的各种写法: pnpm、pnpm@9.15.0、 yarn@4.1.0+sha512.xxx 都认。

每次执行都会在 stderr 打印一行说明: [x] 检测到 pnpm-lock.yaml,使用 pnpm。

国内源说明

x search 默认走国内源 registry.npmmirror.com,因为官方源在国内经常超时。

优先级从高到低:

1--source npm | cn | <URL>命令行
2.xrc.json#registry配置文件
3X_REGISTRY 环境变量整个会话生效
4默认 npmmirror兜底
bash
$ x search react --source npm   # 临时用官方源
$ export X_REGISTRY=https://registry.npmmirror.com

常见问题

它会替代包管理器吗?

不会。x 只是翻译命令,真正的活还是 npm / pnpm / bun / yarn 在干。 所有参数原样透传,所以包管理器的任何特性你都用得上。

为什么有 stderr 上的那行 [x] 检测到…

这是检测提示,说明 x 为什么选了某个包管理器。它走 stderr, 所以不会污染 --json 等需要管道消费的 stdout 输出。

x dev 会和某个包重名吗?

有理论上的可能。比如你的脚本叫 i 或 search, 那 x i 会被解析成安装命令。规避办法:给这类脚本起个不冲突的名字, 或者改用完整的 x run i。

monorepo 里怎么用?

在任意子目录执行 x 都会向上找到项目根再检测。 x find 也会把 workspace 包里的 node_modules 一起扫,位置标为 workspace。

改了代码怎么生效?

npm run build && x --help。 因为是 npm link 装的软链,不用重装。 记住顺序是 build 在前、link 在后。

开发

bash
npm install     # 装依赖
npm run dev     # tsx 直接跑 src/index.ts
npm run lint    # tsc --noEmit 类型检查
npm test        # vitest
npm run build   # tsup 打包到 dist/

技术栈:TypeScript 5(严格模式)+ 全 ESM, cac 生成帮助、execa 执行子进程、 picocolors 着色、find-up 找配置、 which 跨平台定位、tsup 打包、vitest 测试。

GitHub 源码 → MIT