vue-vben-admin 中的 turbo-run:交互式选择包的 monorepo 命令运行器
【免费下载链接】vue-vben-adminA modern vue admin panel built with Vue3, Shadcn UI, Vite, TypeScript, and Monorepo. It's fast!项目地址: https://gitcode.com/GitHub_Trending/vu/vue-vben-admin
turbo-run是 vue-vben-admin 仓库(一个基于 Vue3、Shadcn UI、Vite、TypeScript 的 Monorepo 管理后台)中内置的命令行小工具,它解决了"在 monorepo 的多个应用包中并行运行同一命令"的选择难题:自动探测哪些包定义了目标脚本,弹出交互式列表让你点选,再以pnpm --filter精准投递到指定包执行。读完本文,你将掌握turbo-run的安装、用法、源码调用链,并能在自己的 monorepo 项目中复刻这一套"探测—选择—执行"的 CLI 交互方案。
turbo-run 是什么
在vue-vben-admin这样一个拥有多个可运行应用的 monorepo 仓库里,dev、preview这类脚本往往同时存在于多个包的package.json中。直接用pnpm dev或pnpm run dev无法确定该在哪一个包执行;而手写pnpm --filter @vben/web-antd run dev又需要先记住包名、再逐字敲击,容易出错。
turbo-run(全名@vben/turbo-run)正是为此设计的命令行工具:它允许你在多个包中并行运行命令,并通过交互式界面选择要运行命令的包。按照官方 README 的描述,其核心特性包括:
- 🚀 交互式选择要运行的包
- 📦 支持 monorepo 项目结构
- 🔍 自动检测可用的命令
- 🎯 精确过滤目标包
在 vue-vben-admin 根目录的 package.json 中,它已经被真实接入日常开发流程:
{ "scripts": { "dev": "turbo-run dev", "preview": "turbo-run preview" } }也就是说,开发者只需在仓库根目录执行pnpm dev,即可进入 turbo-run 的交互流程,从多个候选应用中选择一个来启动;pnpm preview则用于选择要本地预览构建产物的应用。
安装与前置条件
安装命令
在 monorepo 根目录通过 pnpm 安装:
pnpm add -D @vben/turbo-run在 vue-vben-admin 中,它是作为 workspace 内部包接入的:根 package.json 声明了"@vben/turbo-run": "workspace:*",源码位于 scripts/turbo-run 目录。该包自身版本为5.8.0,声明为private: true且以MIT协议开源(见 scripts/turbo-run/package.json)。
前置条件
根据原文档的"注意事项"小节,使用前需要确认以下三点:
- 项目使用 pnpm 作为包管理器——turbo-run 最终通过
pnpm --filter执行命令,因此这是硬性前提; - 目标包必须在
package.json中定义了相应的脚本命令——只有包含目标脚本的包才会出现在选择列表中; - 必须在 monorepo 项目的根目录下运行——工具的"自动探测"依赖从当前目录向上回溯找到仓库根,详见下文"源码原理"。
vue-vben-admin 本身即满足这些条件:根 package.json 锁定"packageManager": "pnpm@11.16.0",并通过preinstall脚本(npx only-allow pnpm)强制使用 pnpm。
使用方法
基本语法:
turbo-run [script]其中[script]是希望执行的目标脚本名。例如运行dev命令:
turbo-run dev工具会自动检测哪些包有dev命令,并提供一个交互式界面让你选择要运行的包。整个交互流程为:
- 检测哪些包有
dev命令; - 显示一个交互式选择界面;
- 让你选择要运行命令的包;
- 使用
pnpm --filter在选定的包中运行命令。
在 vue-vben-admin 中的实际效果
以turbo-run dev为例。仓库中apps目录下存在多个可运行应用(包名见各应用 package.json):@vben/web-antd、@vben/web-antdv-next、@vben/web-ele、@vben/web-naive、@vben/web-tdesign,此外还有@vben/docs(文档站)、@vben/playground(演示沙盒)以及@vben/backend-mock(Nitro 后端 Mock)。其中定义了dev脚本的包都会被枚举出来,终端会显示类似如下的选择提示:
? Select the app you need to run [dev]: @vben/web-antd @vben/web-antdv-next @vben/web-ele ❯ @vben/web-naive @vben/web-tdesign选中后即等价于手动执行:
pnpm --filter @vben/web-naive run dev这样就把"记住包名 + 拼接 filter 参数"的机械劳动,变成了一次回车选择。
源码原理:探测 → 选择 → 执行
turbo-run 的完整实现只有两个源文件,非常适合作为学习"CLI 交互工具"的入门范本:入口 src/index.ts 负责命令行解析,核心逻辑 src/run.ts 负责探测与执行。
入口:cac 命令行解析
src/index.ts 使用cac定义命令:
import { consola } from '@vben/node-utils'; import { cac } from 'cac'; import { run } from './run'; try { const turboRun = cac('turbo-run'); turboRun .command('[script]') .usage(`Run turbo interactively.`) .action(async (command: string) => { run({ command }); }); turboRun.usage('turbo-run'); turboRun.help(); turboRun.parse(); } catch (error) { consola.error(error); process.exit(1); }要点:[script]是可选参数(方括号包裹);错误统一交给consola输出并以退出码 1 结束。
核心:run() 的三段式流程
src/run.ts 中的run(options)接收{ command },按"校验参数 → 探测包 → 选择并执行"的顺序工作。
第一步,校验命令参数(L10-L14):
const { command } = options; if (!command) { console.error('Please enter the command to run'); process.exit(1); }未传入脚本名时直接报错退出。
第二步,自动探测"有哪些包定义了该脚本"(L15-L24):
const { packages } = await getPackages(); // 只显示有对应命令的包 const selectPkgs = packages.filter((pkg) => { return (pkg?.packageJson as Record<string, any>)?.scripts?.[command]; });getPackages来自 workspace 内部包@vben/node-utils,其实现见 internal/node-utils/src/monorepo.ts:先从当前工作目录出发向上查找pnpm-lock.yaml定位 monorepo 根(findMonorepoRoot),再委托@manypkg/get-packages收集全仓所有包及其packageJson。这解释了"必须在根目录下运行"的原因——工具的探测基准就是仓库根。
随后按packageJson.scripts[command]精确过滤,只有定义了该脚本的包才会进入候选列表。值得一提的是,源码中曾预留过更细粒度的findApps过滤逻辑(限定apps目录且存在vite.config.mts的包),目前已被注释,可以推断作者在迭代中简化为了"凡是有该脚本的包都可选"。
第三步,交互选择与执行(L26-L55):
let selectPkg: string | symbol; if (selectPkgs.length > 1) { selectPkg = await select<string>({ message: `Select the app you need to run [${command}]:`, options: selectPkgs.map((item) => ({ label: item?.packageJson.name, value: item?.packageJson.name, })), }); if (isCancel(selectPkg) || !selectPkg) { cancel('👋 Has cancelled'); process.exit(0); } } else { selectPkg = selectPkgs[0]?.packageJson?.name ?? ''; } if (!selectPkg) { console.error('No app found'); process.exit(1); } try { await execa('pnpm', [`--filter=${selectPkg}`, 'run', command], { stdio: 'inherit', }); } catch (error: any) { process.exit(error.exitCode || 1); }这里有两个值得注意的分支行为:
- 候选包多于 1 个:使用
@clack/prompts的select弹出交互列表;用户按Ctrl+C取消时,通过isCancel检测并调用cancel优雅退出(退出码 0)。 - 候选包只有 1 个甚至 0 个:跳过交互,直接取唯一包执行;取不到则报
No app found退出。这意味着如果仓库中只有一个包定义了dev,turbo-run dev的行为与直接执行等价,不会多此一举地弹窗。
最终执行调用的是execa('pnpm', ['--filter=<包名>', 'run', command], { stdio: 'inherit' })——execa由@vben/node-utils重新导出(见 internal/node-utils/src/index.ts)。--filter=<包名>是 pnpm 的过滤器语法,stdio: 'inherit'保证子进程的输入输出直接透传到当前终端,开发服务器(如 Vite)的热更新输出可以原样呈现。子进程异常退出时,以子进程的exitCode(兜底为 1)作为自身退出码。
工程化细节:bin 入口与构建
turbo-run 通过tsdown构建为 ESM 产物,配置见 scripts/turbo-run/tsdown.config.ts:
import { defineConfig } from 'tsdown'; export default defineConfig({ clean: true, dts: true, entry: ['src/index.ts'], format: ['esm'], outExtensions: () => ({ dts: '.d.ts', }), });format: ['esm']输出 ESM 格式并附带类型声明,与根 package.json 的"type": "module"保持一致。
CLI 命令的注册点在 scripts/turbo-run/package.json:
"bin": { "turbo-run": "./bin/turbo-run.mjs" }bin字段把turbo-run命令映射到./bin/turbo-run.mjs(该入口文件在源码构建后生成,运行时由 dist 产物驱动),配合files: ["dist"]只发布构建目录。依赖方面,它引用了@clack/prompts(交互提示)、@vben/node-utils(workspace 内部工具)与cac(命令行解析),版本统一由根 pnpm-workspace.yaml 的catalog字段托管,例如@clack/prompts: ^1.7.0、cac: ^7.0.0。
在 monorepo 中,postinstall脚本pnpm -r run --if-present stub会在依赖安装后自动执行各 workspace 包的 stub 构建,确保turbo-run的 bin 入口在安装阶段即可用。
常见问题与排查思路
结合原文档"注意事项"和源码行为,可以梳理出以下常见问题的排查方向:
| 现象 | 可能原因 | 排查/解决 |
|---|---|---|
提示Please enter the command to run | 未传入脚本名参数 | 正确写法:turbo-run dev |
提示No app found | 仓库中没有包定义该脚本,或未在 monorepo 根目录运行 | 确认目标包的package.json中scripts包含该命令;回到根目录执行 |
| 选择列表为空/候选过少 | 脚本名拼写不一致 | 检查各包scripts键名是否完全一致(如dev与dev:antd是不同脚本) |
| 选中后命令立即失败 | 目标包脚本本身报错 | 直接执行pnpm --filter <包名> run <script>复现,错误码会原样透传 |
找不到turbo-run命令 | 包未安装或 bin 未链接 | 在根目录执行pnpm install,确认@vben/turbo-run出现在根 package.json 的 devDependencies |
值得强调的是,vue-vben-admin 中针对单一应用的启动还提供了绕过交互的快捷方式(见根 package.json),例如pnpm dev:antd等价于pnpm -F @vben/web-antd run dev。当你明确知道要跑哪个应用时,可以用这些固定脚本;而turbo-run的价值在于不确定或经常切换目标时的交互式体验。
总结
turbo-run 用不到百行源码,把 monorepo 下"多包同脚本"的启动痛点化解为一次键盘选择:以@manypkg/get-packages探测包、以@clack/prompts呈现选择、以execa执行pnpm --filter,三层各司其职(src/run.ts)。对于 vue-vben-admin 的开发者,它意味着pnpm dev不再需要先回忆包名;对于任何 pnpm monorepo 项目,它都是一份可以照搬的"交互式命令运行器"参考实现。若需在自己项目中复刻,只需满足三个前提:使用 pnpm、包内脚本就绪、在仓库根目录运行。
【免费下载链接】vue-vben-adminA modern vue admin panel built with Vue3, Shadcn UI, Vite, TypeScript, and Monorepo. It's fast!项目地址: https://gitcode.com/GitHub_Trending/vu/vue-vben-admin
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考