pnpm 指定工作目录执行命令(-C / --dir / -w)
场景
当前我们在根目录,想要执行子目录中 package.json 中的 scripts 命令,或者我们在某个很深的子目录想要执行根目录的命令
解决方法
指定目录执行命令
pnpm -C play run clean
pnpm --dir single/child/childs clean
配置项:-C <path>, --dir <path> 在 <path> 中启动 pnpm ,而不是当前的工作目录。
执行主目录命令
pnpm clean -w
-w(--workspace-root): 在工作空间的根目录中启动 pnpm ,而不是当前的工作目录。
这三个参数的差别
-C / --dir 和 -w 看起来都是「换个目录执行」,但机制不一样:
-C <path>是纯粹的目录切换,效果等同于cd <path> && pnpm ...。路径可以是任意相对或绝对路径,跟工作区配置无关,甚至可以指向一个不属于当前 monorepo 的项目。-w是语义化的定位,pnpm 会从当前目录往上找pnpm-workspace.yaml,找到的那一层就是根。所以不管我现在钻到多深的子目录,-w都能准确回到根,不用数几个../。
顺带说一句,-w 在 pnpm add 里还有一层含义:把依赖装到工作区根的 package.json。pnpm 默认禁止在根目录直接 pnpm add,会报 Running this command will add the dependency to the workspace root, which might not be what you want,必须显式加 -w 表态:
# 把 eslint 装到工作区根的 devDependencies
pnpm add -Dw eslint
参数位置有讲究
上面示例里 pnpm -C play run clean 把 -C 放在了子命令前面,而 pnpm clean -w 把 -w 放在后面。这两种写法 pnpm 都能解析,但放在子命令前面更稳妥。原因是 pnpm 会把子命令后面的参数往下透传给被执行的脚本,位置靠后的标志有时会被当成脚本自己的参数。真正麻烦的是给脚本传参数的场景:
# 想给 vite 传 --port,必须用 -- 分隔
pnpm -C play run dev -- --port 3000
-- 之后的所有内容 pnpm 一概不解析,原样交给 dev 脚本。没有这个分隔符的话,--port 有可能被 pnpm 自己吞掉。
run 关键字能不能省
能省,但有前提。pnpm clean 在 clean 不是 pnpm 内置命令时,会被当作 pnpm run clean 处理。问题在于脚本名撞上内置命令时就出事了——如果 package.json 里有个叫 install 或 add 的脚本,pnpm install 执行的是 pnpm 的安装逻辑,不是你的脚本。所以我写文档和 CI 配置时一律带上 run,避免这种歧义。
用 --filter 替代 -C
-C 是按路径定位,--filter 是按包名定位,在 monorepo 里后者往往更合适:
# 按包名执行(不用关心目录在哪)
pnpm --filter @app/play run clean
# 该包 + 它依赖的所有包一起执行
pnpm --filter @app/play... run build
# 依赖该包的所有包(反向)
pnpm --filter ...@app/play run build
# 按目录过滤,效果接近 -C 但支持通配
pnpm --filter "./packages/**" run lint
... 那三个点的位置决定方向:跟在包名后面是「带上它的依赖」,写在包名前面是「带上依赖它的包」。改了某个基础库想验证下游有没有炸,用 ...@app/utils 这种反向过滤一次跑完最省事。
和 -C 最本质的区别是:-C 一次只能进一个目录,--filter 可以一次命中多个包并批量执行;而且 --filter 走的是包依赖图,pnpm 会按拓扑序排好执行顺序,被依赖的包先构建完再构建依赖方。-C 没有这层保证。

还没有留言,来发表第一条吧~