Skip to content

Turbo 任务 ​

本页说明「入口 package.json、fnos-dsh-cli CLI、Turbo 和 workspace package 任务」的分工与调用顺序。修改根脚本、turbo.json 或包内任务时,先确认是否破坏了这条链路。

四层职责 ​

层级位置职责
用户入口根 package.json提供稳定、简短的 start、build、check、version 等命令;不重复实现包任务
任务路由tooling/fnos-dsh-cli/program.ts 暴露 Commander 实例,各 commands/*.ts 注册命令并实现 action,处理交互提示、参数解析、文档构建、版本维护和 Turbo 调用
任务编排turbo.json声明 build、dev、typecheck、test、check 的依赖、缓存和输出
实际任务各 workspace 的 package.json执行 tsdown、tsc、vitest 等包自己的任务

根脚本是入口,不是实现层:

json
{
  "scripts": {
    "build": "pnpm exec fnos-dsh-cli build",
    "check": "pnpm exec fnos-dsh-cli check",
    "publish": "pnpm exec fnos-dsh-cli publish",
    "start": "pnpm exec fnos-dsh-cli start"
  }
}

调度过程 ​

每次 CLI 调用 Turbo 后,Turbo 不会按目录顺序执行脚本,而是先解析过滤范围和 workspace 依赖,再按任务图调度。以 check 为例,build、typecheck 和 test 会按 turbo.json 中的依赖关系执行;没有依赖关系的就绪任务可以并行。

mermaid
flowchart TD
  invoke["CLI: turbo run task"]
  filter["1. 解析 task 与 --filter"]
  depGraph["2. 展开 workspace 依赖图"]
  hash["3. 计算任务哈希"]
  cache["4. 查询缓存"]
  cacheStatus{"缓存命中?"}
  restore["命中:恢复 outputs"]
  deps["未命中:调度依赖任务"]
  ready["依赖完成:目标任务就绪"]
  run["执行 package.json 脚本"]
  summary["汇总状态与 outputs"]

  invoke --> filter --> depGraph --> hash --> cache --> cacheStatus
  cacheStatus -->|命中| restore
  cacheStatus -->|未命中| deps
  deps --> ready --> run --> summary
  restore --> summary

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 和文档可以多选;参数模式则直接进入对应分支:

bash
pnpm run build
pnpm run build -- --plugin fnos
pnpm run build -- --fpk --app fn-deepseek-harness
pnpm run build -- --docs
mermaid
flowchart TD
  command["pnpm run build"]
  cli["fnos-dsh-cli build"]
  select{"构建目标?"}
  plugins["harness 插件:多选目标"]
  fpk["FPK:多选应用"]
  docs["文档:VitePress"]
  pluginFilter["turbo run build --filter=harness 插件..."]
  pluginDeps["^build:共享 UI 依赖"]
  pluginRun["执行 harness 插件 package.json build"]
  fpkStatus{"包含 DSH FPK?"}
  gateway["是:先执行 Gateway build:app"]
  fpkSkip["否:跳过 Gateway"]
  fpkRun["每个应用执行 fnpack build"]
  docsRun["执行 vitepress build docs"]
  summary["汇总构建结果"]
  status{"构建成功?"}
  result["是:返回成功"]
  fail["否:返回非零状态"]

  command --> cli --> select
  select -->|harness 插件| plugins
  select -->|FPK| fpk
  select -->|文档| docs
  plugins --> pluginFilter --> pluginDeps --> pluginRun --> summary
  fpk --> fpkStatus
  fpkStatus -->|是| gateway
  fpkStatus -->|否| fpkSkip
  gateway --> fpkRun
  fpkSkip --> fpkRun
  fpkRun --> summary
  docs --> docsRun --> summary
  summary --> status
  status -->|是| result
  status -->|否| fail

start ​

start 是用户可见的开发启动入口。CLI 先让用户选择插件、文档和/或本地 DSH Web,再把它们一次性交给同一个 turbo watch:插件与文档走 dev,DSH Web 走仓库根任务 //#dev:web,因此三类任务都显示在同一个 TUI 中。

三类目标共用同一个 turbo watch,因此始终保留 TUI。这里不能把 DSH Web 放到 Turbo 之外另起进程——它会占住终端,把 TUI 挤掉。启动前的 profile 链接逻辑见本地 DSH Web。

mermaid
flowchart TD
  command["pnpm run start"]
  cli["fnos-dsh-cli start"]
  select{"启动目标?(可多选)"}
  plugins["harness 插件:选择目标"]
  docs["文档:VitePress"]
  web["DSH Web"]
  webPlugins["链接仓库插件进本地 profile(先 Turbo 构建)"]
  webUi["//#dev:web 与 dev 各占 TUI 一行"]
  webEnv["仓库根 .dsh 作为 DSH_HOME,端口 8070"]
  turbo["单次 turbo watch:dev + //#dev:web(同一 TUI)"]
  filters["组合 filters:./docs + harness 插件..."]
  depDev["dsh-semi-ui dev(一次性构建,persistent: false)"]
  depDone["lib/** 就绪"]
  pluginWatch["harness 插件:tsdown --watch(常驻)"]
  docsWatch["docs package:vitepress dev"]
  changeStatus{"有源文件变化?"}
  rebuild["是:依赖变化时重启插件 dev,重新生成 lib/**"]
  keep["否:保持监听"]
  services["开发服务持续运行"]

  command --> cli --> select
  select -->|harness 插件| plugins
  select -->|文档| docs
  select -->|DSH Web| web
  web --> webPlugins --> webEnv
  plugins --> turbo
  docs --> turbo
  webEnv --> turbo
  turbo --> webUi
  turbo --> filters
  filters --> depDev --> depDone --> pluginWatch
  filters --> docsWatch --> services
  pluginWatch --> changeStatus
  changeStatus -->|是| rebuild
  changeStatus -->|否| keep
  rebuild --> changeStatus
  keep --> changeStatus
  pluginWatch --> services

typecheck ​

typecheck 由根脚本直接调用 Turbo,不经过 fnos-dsh-cli 交互层。

mermaid
flowchart TD
  command["pnpm run typecheck"]
  turbo["turbo run typecheck --filter=目标包"]
  depGraph["解析 workspace 依赖"]
  deps["^typecheck:先检查依赖包"]
  target["执行目标包 tsc -p tsconfig.json"]
  status{"检查通过?"}
  result["是:返回成功"]
  fail["否:返回非零状态"]

  command --> turbo --> depGraph --> deps --> target --> status
  status -->|是| result
  status -->|否| fail

test ​

test 由根脚本直接调用 Turbo,先满足当前包的 build 依赖,再启动 Vitest。

mermaid
flowchart TD
  command["pnpm run test"]
  turbo["turbo run test --filter=目标包"]
  depGraph["读取 test 任务图"]
  build["dependsOn:先完成当前包 build"]
  run["vitest run --config vitest.config.ts"]
  status{"测试通过?"}
  result["是:返回成功"]
  fail["否:返回非零状态"]

  command --> turbo --> depGraph --> build --> run --> status
  status -->|是| result
  status -->|否| fail

check ​

check 与 build 一样由 fnos-dsh-cli 先处理多选目标,再并行执行直接检查和 Turbo 检查。

mermaid
flowchart TD
  command["pnpm run check"]
  cli["fnos-dsh-cli check"]
  select{"检查目标?(可多选)"}
  sdd["SDD:需求、计划和链接"]
  docs["文档:VitePress"]
  packages["共享包"]
  plugins["harness 插件"]
  directSdd["执行 SDD checker"]
  directDocs["执行 vitepress build docs"]
  turbo["turbo run check --filter=packages/plugins"]
  turboGraph["展开 check 依赖图"]
  build["dependsOn:build"]
  typecheck["dependsOn:^typecheck(仅依赖包)"]
  checkRun["执行各包 package.json 的 check(内含 typecheck 与 test)"]
  summary["汇总所有检查结果"]
  status{"检查通过?"}
  result["是:返回成功"]
  fail["否:返回非零状态"]

  command --> cli --> select
  select -->|SDD| sdd
  select -->|文档| docs
  select -->|包| packages
  select -->|harness 插件| plugins
  sdd --> directSdd --> summary
  docs --> directDocs --> summary
  packages --> turbo
  plugins --> turbo
  turbo --> turboGraph
  turboGraph --> build
  turboGraph --> typecheck
  build --> checkRun
  typecheck --> checkRun
  checkRun --> summary
  summary --> status
  status -->|是| result
  status -->|否| fail

检查任务交互 ​

执行 pnpm run check 会进入多选提示;Agent、CI 或提交脚本应使用参数避免交互:

bash
pnpm run check -- --sdd
pnpm run check -- --docs
pnpm run check -- --packages --plugins
pnpm run check -- --tooling
pnpm run check -- --all

下图展示一次 --all 检查的主要交互。SDD 和文档检查由 CLI 直接处理,包、插件和构建工具检查统一交给一次 Turbo 调度。

mermaid
sequenceDiagram
  participant developer as 开发者
  participant root as 根 package.json
  participant cli as fnos-dsh-cli CLI
  participant checker as SDD checker
  participant vitepress as VitePress
  participant turbo as Turbo
  participant semi as dsh-semi-ui
  participant packages as 共享包
  participant plugins as harness 插件
  participant tooling as 构建工具

  developer->>root: pnpm run check -- --all
  root->>cli: pnpm exec fnos-dsh-cli check --all
  cli->>checker: 校验需求、计划和链接
  cli->>vitepress: 构建 docs
  cli->>turbo: pnpm exec turbo run check --filter=./packages/* --filter=./plugins/* --filter=./tooling/*
  turbo->>semi: 先执行 package.json 的 build
  turbo->>packages: 执行 package.json 的 check
  turbo->>plugins: 执行 harness 插件 package.json 的 check
  turbo->>tooling: 执行构建工具 package.json 的 check
  packages->>turbo: 返回检查结果
  plugins->>turbo: 返回检查结果
  tooling->>turbo: 返回检查结果
  turbo->>cli: 汇总任务结果
  checker->>cli: 返回静态检查结果
  vitepress->>cli: 返回文档构建结果
  cli->>developer: 返回成功或非零退出码

tooling/* 的 check 在 turbo.json 里单独声明了 inputs:它的用例会读取 apps/、docs/、plugins/、packages/ 下的文件(发布清单、文档版本号、兼容性清单),而 Turbo 默认只按该包自身文件计算哈希。不补 inputs 时本地缓存会把「清单已漂移」当成命中缓存而报假绿;CI 没有远程缓存所以不受影响,但开发者本机需要这份声明。

构建任务交互 ​

插件构建使用依赖过滤器,例如:

bash
pnpm run build -- --plugin fnos

CLI 将目标插件转换为 Turbo filter。由于插件在自己的 package.json 中声明了 @tnnevol/dsh-semi-ui workspace 依赖,turbo.json 的 build.dependsOn: ["^build"] 会自动先构建共享 UI,不需要在根脚本中手工编排。

mermaid
sequenceDiagram
  participant developer as 开发者
  participant root as 根 package.json
  participant cli as fnos-dsh-cli CLI
  participant turbo as Turbo
  participant semi as dsh-semi-ui
  participant plugin as dsh-fnos

  developer->>root: pnpm run build -- --plugin fnos
  root->>cli: pnpm exec fnos-dsh-cli build --plugin fnos
  cli->>turbo: turbo run build --filter=dsh-fnos...
  turbo->>semi: package.json build
  turbo->>plugin: package.json build
  turbo->>cli: 返回构建结果
  cli->>developer: 输出构建状态

... 表示把目标包的依赖纳入过滤范围;实际是否执行依赖任务,仍由 workspace 依赖声明和 Turbo 任务图决定。

开发启动任务 ​

bash
# 交互选择插件和/或文档
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 中分别显示。

mermaid
sequenceDiagram
  participant developer as 开发者
  participant root as 根 package.json
  participant cli as fnos-dsh-cli CLI
  participant turbo as Turbo
  participant semi as dsh-semi-ui
  participant plugin as dsh-fnos

  developer->>root: pnpm run start -- --plugin fnos
  root->>cli: pnpm exec fnos-dsh-cli start --plugin fnos
  cli->>turbo: turbo watch dev --filter=dsh-fnos...
  turbo->>semi: 先执行一次性 tsdown(persistent: false)
  semi->>turbo: lib/** 就绪
  turbo->>plugin: 再启动 tsdown --watch
  semi->>turbo: 依赖源文件变化,重新一次性构建 lib/**
  turbo->>plugin: interruptible 重启插件 dev
  plugin->>turbo: 重新生成插件 Bundle
  turbo->>cli: 持续运行直到终止

编写和修改规则 ​

  1. 根 package.json 只增加稳定入口,不把 cd、重复构建依赖或包内实现写入根脚本。
  2. 包的实际任务放在对应 workspace 的 package.json;任务名称要能被 Turbo 统一调用。
  3. 新增任务后同步在 turbo.json 声明 dependsOn、outputs、cache 或 persistent。
  4. workspace 依赖必须真实写入包的 dependencies 或 devDependencies,否则 ^build、^dev 无法推导依赖顺序。
  5. 共享包作为依赖参与 dev 时必须是一次性任务:persistent: false 且在包内 turbo.json 覆盖,否则 ^dev 会因「persistent task cannot be depended on」报错,或与消费方并行写入 lib/**。
  6. package.json 和工作流中使用 turbo run;持续开发使用 turbo watch。
  7. 同一类检查尽量通过一次 Turbo 调度传入多个 filter,避免共享依赖被多个 Turbo 进程重复执行。

常用验证 ​

bash
pnpm run check -- --sdd --docs
pnpm run check -- --packages --plugins
pnpm run check -- --all
pnpm run build -- --docs

相关页面 ​

基于 VitePress 构建