Turbo 任务
本页说明「入口 package.json、fn-apps-cli CLI、Turbo 和 workspace package 任务」的分工与调用顺序。修改根脚本、turbo.json 或包内任务时,先确认是否破坏了这条链路。
四层职责
| 层级 | 位置 | 职责 |
|---|---|---|
| 用户入口 | 根 package.json | 提供稳定、简短的 start、build、check、version 等命令;不重复实现包任务 |
| 任务路由 | tooling/fn-os-apps-cli/ | program.ts 暴露 Commander 实例,各 commands/*.ts 注册命令并实现 action,处理交互提示、参数解析、文档构建、版本维护和 Turbo 调用 |
| 任务编排 | turbo.json | 声明 build、dev、typecheck、test、check 的依赖、缓存和输出 |
| 实际任务 | 各 workspace 的 package.json | 执行 tsdown、tsc、vitest 等包自己的任务 |
根脚本是入口,不是实现层:
{
"scripts": {
"build": "pnpm exec fn-apps-cli build",
"check": "pnpm exec fn-apps-cli check",
"publish": "pnpm exec fn-apps-cli publish",
"start": "pnpm exec fn-apps-cli start"
}
}调度过程
每次 CLI 调用 Turbo 后,Turbo 不会按目录顺序执行脚本,而是先解析过滤范围和 workspace 依赖,再按任务图调度。以 check 为例,build、typecheck 和 test 会按 turbo.json 中的依赖关系执行;没有依赖关系的就绪任务可以并行。
turbo.json 关键约定
build通过^build先调度 workspace 依赖的构建。dev通过^dev先完成依赖包的dev;被依赖包用persistent: false做一次性构建,插件自身保持常驻并在interruptible: true下随依赖变化重启。typecheck通过^typecheck先调度依赖包的类型检查。test依赖当前包的build,避免测试使用过期产物。check依赖当前包的build与依赖包的typecheck(^typecheck);各包的check脚本本身已经是typecheck && test,因此不再把typecheck、test同时写进dependsOn,否则同一件事会被调度两次。lint是仓库级根任务(//#lint):ESLint 只使用根eslint.config.ts,没有包定义lint脚本,写成普通任务只会匹配到空集。build声明env: ["NODE_ENV"]:插件客户端 bundle 在 tsdown 里用process.env.NODE_ENV做define,不声明会让不同NODE_ENV共用同一份缓存。docs包的build用包级turbo.json覆盖outputs为.vitepress/dist/**:VitePress 的实际产物不在dist/,不覆盖会让 Turbo 报「no output files found」,文档产物既不入缓存也无法恢复。docs包的dev声明interactive: true:TUI 的「interact with task」把 stdin 交给该任务,VitePress 快捷键才生效;interactive与cache: true互斥,且没有 TUI 时 Turbo 直接报错而不降级,因此start必须按 TTY 决定是否把它放进turbo watch。- 全局
ui设置为tui;多任务并行时使用终端任务面板分别查看日志,避免不同任务的输出混合在同一条流中。 - 交互命令会先完成所有主选项和子选项询问,再统一启动已选任务,避免任务执行期间继续等待输入。
各任务步骤
build
交互模式先选择构建目标,插件、FPK 和文档可以多选;参数模式则直接进入对应分支:
pnpm run build
pnpm run build -- --plugin fnos
pnpm run build -- --fpk --app fn-deepseek-harness
pnpm run build -- --docsstart
start 是用户可见的开发启动入口。CLI 先让用户选择插件、文档和/或本地 DSH Web,再把它们一次性交给同一个 turbo watch:插件与文档走 dev,DSH Web 走仓库根任务 //#dev:web,因此三类任务都显示在同一个 TUI 中。
三类目标共用同一个 turbo watch,因此始终保留 TUI。这里不能把 DSH Web 放到 Turbo 之外另起进程——它会占住终端,把 TUI 挤掉。启动前的 profile 链接逻辑见本地 DSH Web。
typecheck
typecheck 由根脚本直接调用 Turbo,不经过 fn-apps-cli 交互层。
test
test 由根脚本直接调用 Turbo,先满足当前包的 build 依赖,再启动 Vitest。
check
check 与 build 一样由 fn-apps-cli 先处理多选目标,再并行执行直接检查和 Turbo 检查。
检查任务交互
执行 pnpm run check 会进入多选提示;Agent、CI 或提交脚本应使用参数避免交互:
pnpm run check -- --sdd
pnpm run check -- --docs
pnpm run check -- --packages --plugins
pnpm run check -- --all下图展示一次 --all 检查的主要交互。SDD 和文档检查由 CLI 直接处理,包检查统一交给一次 Turbo 调度。
构建任务交互
插件构建使用依赖过滤器,例如:
pnpm run build -- --plugin fnosCLI 将目标插件转换为 Turbo filter。由于插件在自己的 package.json 中声明了 @tnnevol/dsh-semi-ui workspace 依赖,turbo.json 的 build.dependsOn: ["^build"] 会自动先构建共享 UI,不需要在根脚本中手工编排。
... 表示把目标包的依赖纳入过滤范围;实际是否执行依赖任务,仍由 workspace 依赖声明和 Turbo 任务图决定。
开发启动任务
# 交互选择插件和/或文档
pnpm run start
# Agent 或脚本直接指定
pnpm run start -- --plugin fnos
pnpm run start -- --docs插件和文档均通过同一个 turbo watch dev 进程启动:@tnnevol/dsh-semi-ui 的 dev 是一次性 tsdown 构建并先于插件完成,插件 dev 为常驻 tsdown --watch,docs workspace 的 dev 为 vitepress dev。它们在 TUI 中分别显示。
编写和修改规则
- 根
package.json只增加稳定入口,不把cd、重复构建依赖或包内实现写入根脚本。 - 包的实际任务放在对应 workspace 的
package.json;任务名称要能被 Turbo 统一调用。 - 新增任务后同步在
turbo.json声明dependsOn、outputs、cache或persistent。 - workspace 依赖必须真实写入包的
dependencies或devDependencies,否则^build、^dev无法推导依赖顺序。 - 共享包作为依赖参与
dev时必须是一次性任务:persistent: false且在包内turbo.json覆盖,否则^dev会因「persistent task cannot be depended on」报错,或与消费方并行写入lib/**。 package.json和 Workflow 中使用turbo run;持续开发使用turbo watch。- 同一类检查尽量通过一次 Turbo 调度传入多个 filter,避免共享依赖被多个 Turbo 进程重复执行。
常用验证
pnpm run check -- --sdd --docs
pnpm run check -- --packages --plugins
pnpm run check -- --all
pnpm run build -- --docs