Langfuse 单仓中的 Turborepo 过滤模式实战:--filter与--affected完整指南
【免费下载链接】langfuse🪢 Open source AI engineering platform: LLM evals, observability, metrics, prompt management, playground, datasets. Integrates with OpenTelemetry, LangChain, OpenAI SDK, LiteLLM, and more. 🍊YC W23项目地址: https://gitcode.com/GitHub_Trending/la/langfuse
本指南以 Turborepo 过滤(filtering)的常见模式为主线,结合 Langfuse 开源仓库的真实单仓结构(web、worker、@langfuse/shared等 workspace 包)、turbo.json任务编排以及 CI 流水线中的实际用法,讲解如何在大型 monorepo 中精准圈定任务执行范围。读完本文,你将掌握包名过滤、依赖方向过滤、变更集过滤(git 比较语法与--affected)、目录与 Scope 过滤、排除与组合过滤,并能熟练使用--dry调试过滤器,在本地与 CI 中实现"只跑该跑的"。
背景:Langfuse 的 Turborepo 单仓结构
Langfuse 仓库是一个典型的 JavaScript/TypeScript monorepo,由 Turborepo(根目录 package.json 中声明"turbo": "2.10.5")统一编排构建、测试、类型检查等任务。其 workspace 成员在 pnpm-workspace.yaml 中声明:
packages: - "web" # Next.js 前端应用 - "worker" # 后台 worker 应用 - "packages/**" # 内部共享包,如 @langfuse/shared、@repo/eslint-plugin - "ee" # 企业版源码包其中与本指南过滤示例直接相关的包名有:
web:Next.js 应用,依赖@langfuse/shared("@langfuse/shared": "workspace:*"),见 web/package.jsonworker:后台任务应用,依赖@langfuse/shared与@repo/langfuse-skills,见 worker/package.json@langfuse/shared:核心共享库,见 packages/shared/package.json@repo/eslint-config、@repo/typescript-config、@repo/eslint-plugin、@repo/in-app-agent-sandbox-runtime、@repo/langfuse-skills:位于packages/**下的内部包
根目录 turbo.json 定义了build、test、lint、typecheck、dev等任务;根目录 package.json 的脚本只做一件事——委托给 turbo,例如"build": "turbo run build"、"dev:web": "turbo run dev --filter=web"、"dev:worker": "turbo run dev --filter=worker"。这正是"过滤"发挥作用的场景:全量执行固然简单,但在一个拥有几十个包的仓库里,正确地缩小执行范围直接决定了构建与 CI 的效率。
需要说明的是:turbo run <task> --filter=<selector>只决定"在哪些包里跑这个任务",而任务之间的先后顺序仍由turbo.json中的dependsOn依赖图决定,二者各司其职。
单包过滤:精确锁定一个包
最常见的场景是只对某个包执行任务。文档给出的基础写法是包名精确匹配:
turbo run build --filter=web turbo run test --filter=@acme/api对照 Langfuse 仓库,这两条命令可以落地为:
# 只构建 web 应用(其依赖需要预先构建的场景见下一节) turbo run build --filter=web # 只对 @langfuse/shared 跑测试 turbo run test --filter=@langfuse/shared几个要点:
--filter=<package-name>匹配的是package.json中的name字段,而不是目录名。Langfuse 中web包的name就是"web",而共享库的name是"@langfuse/shared"。- 短别名
-F与--filter等价:turbo run build -F web。 - 多个
--filter之间是并集关系——只要命中任意一个过滤器就会被选中:
# 同时运行 web 和 worker 的构建 turbo run build --filter=web --filter=worker在 Langfuse 的根脚本里已有这样的先例:package.json 中"dev:web": "turbo run dev --filter=web"、"dev:worker": "turbo run dev --filter=worker",正是通过过滤器把 dev 任务限定到单一应用,避免同时拉起两个 dev server。
依赖方向过滤:...与^的语义
单包过滤只圈定一个包,但在 monorepo 中,改一个共享包往往需要"连带"处理它的依赖方或被依赖方。文档将这类场景归纳为四种方向性写法:
| 语法 | 含义 |
|---|---|
pkg... | 包本身 + 它的全部依赖(dependencies) |
...pkg | 包本身 + 它的全部被依赖方(dependents) |
...pkg... | 依赖 + 包本身 + 被依赖方,全家桶 |
^pkg... | 仅依赖,排除包本身 |
...^pkg | 仅被依赖方,排除包本身 |
包 + 依赖:web...
turbo run build --filter=web...含义:构建web,并且先构建它所依赖的一切(即@langfuse/shared、@repo/eslint-config等 workspace 依赖)。文档明确指出它的用途:确保目标包的所有依赖在它之前完成构建。
这在 Langfuse 仓库中是真实需求:web的"build": "INLINE_RUNTIME_CHUNK=false dotenv -e ../.env -- next build"依赖@langfuse/shared先产出dist/(见 web/package.json 与 packages/shared/package.json 的"main": "./dist/src/index.js")。若@langfuse/shared尚未构建,直接turbo run build --filter=web会因找不到dist而失败,所以需要--filter=web...。
仓库的 CI 中还有一条精妙的反向用法(见 .github/workflows/pipeline.yml 的tests-clientjob):
# Build client test dependencies: 只构建 web 的依赖,不构建 web 自身 pnpm turbo run build --filter=web^...注释原文说明了意图:"Client tests import workspace packages directly. Build only web's dependencies (not the Next.js app) so cold caches have package entry points without paying for an unnecessary production web build."——web^...精确表达"web 的全部依赖、但不含 web",从而在冷缓存 CI 上省掉一次不必要的生产构建。这是^排除语义在真实仓库中的教科书式应用。
包 + 被依赖方:...ui
turbo run test --filter=...ui含义:运行所有依赖ui这个库的包(及其本身)的测试。文档强调其典型场景:改动共享包后,验证所有消费方。
映射到 Langfuse:若你修改了@langfuse/shared,其消费者包括web与worker(两者都声明了"@langfuse/shared": "workspace:*")。要一次性回归所有消费者,可以写:
turbo run test --filter=...@langfuse/shared这会选中@langfuse/shared、web、worker(以及任何其他直接或间接依赖它的包)。
仅被依赖方:...^ui
turbo run test --filter=...^ui含义:测试依赖ui的包,但不测试ui本身。当共享库自身的测试已经在其他 job 跑过、只想验证下游消费方时,这个写法可以避免重复。对应 Langfuse 即--filter=...^@langfuse/shared。
变更包过滤:基于 git 比较的[ref]语法
上述方向性过滤解决"按依赖图选包",而开发与 CI 中更常见的需求是"只跑有文件变更的包"。文档给出两条路径:--filter的 git 比较语法,以及推荐的首选方案--affected。
自上次提交以来的变更包
turbo run lint --filter=[HEAD^1][HEAD^1]是 git ref 比较语法:选中自HEAD^1之后发生过文件变更的所有包。注意,方括号内是 git ref,而非包名。
自某个分支点以来的变更包
turbo run lint --filter=[main...HEAD][main...HEAD]使用git diff main...HEAD(三点比较,以main与HEAD的共同祖先为基准)计算变更集。
对照仓库的 ci-runtime-analyst.md(CI 运行时分析文档),其中明确提示开发者用npx turbo run build --dry-run或受影响的过滤命令来验证 turbo.json 改动的影响面,可见该语法在仓库日常开发中被反复使用。
git 比较的完整变体
结合同目录语法参考 .agents/skills/turborepo/references/filtering/RULE.md,[ref]可以与方向符号组合出四种语义:
| 语法 | 含义 |
|---|---|
[ref] | 自ref以来有变更的包(仅本身) |
...[ref] | 变更包 + 它们的被依赖方(dependents) |
[ref]... | 变更包 + 它们的依赖(dependencies) |
...[ref]... | 依赖 + 变更包 + 被依赖方,全部相关包 |
# 变更包 + 被依赖方(等价于 --affected) turbo run build --filter=...[origin/main] # 仅变更包,不带被依赖方 turbo run build --filter=[origin/main] # 变更包 + 它们导入的依赖 turbo run build --filter=[origin/main]... # 任意两个提交之间的变更 turbo run build --filter=[a1b2c3d...e4f5g6h]变更 + 被依赖方:--affected首选方案
文档明确把--affected定位为"运行变更包及其被依赖方的首选方式":
turbo run build test --filter=...[HEAD^1]等价于:
turbo run build test --affected--affected自动将当前分支与默认分支(通常是main或master)比较,然后运行:
- 有文件变更的包;
- 依赖这些变更包的包(被依赖方)。
为什么必须包含被依赖方?文档给出的解释非常直观:如果改了@repo/ui,那么导入@repo/ui的包(比如apps/web)必须重新跑任务,以验证它们在共享库变更后仍然工作。在 Langfuse 中同理:改了@langfuse/shared的一个导出,就必须回归web与worker。
--affected还支持自定义基准与当前状态:
# 以 origin/develop 为基准分支 turbo run build --affected --affected-base=origin/develop # 以 HEAD~5 为当前状态 turbo run build --affected --affected-head=HEAD~5目录过滤:按文件路径圈定范围
不依赖包名,直接用仓库路径选择包:
# apps 目录下的所有包 turbo run build --filter=./apps/* # 精确指定两个目录 turbo run build --filter=./apps/web --filter=./apps/api路径过滤对"目录即边界"的仓库特别直观。Langfuse 的目录布局是web/、worker/、packages/*、ee/,因此可以写成:
# 构建 packages/ 下所有共享包 turbo run build --filter=./packages/* # 同时构建 web 与 worker 两个应用 turbo run build --filter=./web --filter=./worker注意:路径过滤器匹配的是包目录,./packages/*中的*是 glob,会命中packages/下所有直接子目录(shared、eslint-plugin、in-app-agent-sandbox-runtime等)。
Scope 过滤:按命名空间批量选中
当包名遵循统一的 scope 前缀时,用 glob 批量选中:
# @acme scope 下的所有包 turbo run build --filter=@acme/* # 任意以 -app 结尾的包名 turbo run build --filter=*-app对应到 Langfuse:仓库中的共享包均以@langfuse/或@repo/为 scope,因此:
# 所有 @langfuse scope 的包(@langfuse/shared 等) turbo run build --filter=@langfuse/* # 所有 @repo scope 的包(eslint-config、typescript-config、eslint-plugin 等) turbo run lint --filter=@repo/*Scope glob 与名字 glob(*-app)可以混用,命中任意模式的包都会被选中。
排除过滤:用!做减法
过滤器支持取反,在并集语义下实现对"大部分包"场景的快速排除:
# apps 下所有包,除 admin 外 turbo run build --filter=./apps/* --filter=!admin # 全仓库排除两个包 turbo run lint --filter=!legacy-app --filter=!deprecated-pkg在 Langfuse 中可以这样用:
# 构建所有包,但跳过 ee(企业版包)的构建 turbo run build --filter=!ee # 全仓库 lint,但跳过 worker(例如只想先看 web 与共享包) turbo run lint --filter=!worker要点:排除必须与至少一个正向过滤器组合才有意义;--filter=!worker单独使用相当于"选中除了 worker 之外的所有包"(Turbo 会在没有任何正向过滤器时把全仓库视为选择范围,再扣掉被排除的包)。!同样支持与方向符号组合,例如--filter=!web...表示"web 及其依赖之外的所有包"。
复杂组合:并集与嵌套
真实场景往往需要叠加多个维度。文档给出了两个典型例子:
变更的 apps + 它们的被依赖方
turbo run build --filter=...[HEAD^1] --filter=./apps/*语义:...[HEAD^1]选中"自上次提交以来变更的包 + 其被依赖方";./apps/*再并上 apps 下全部包。最终执行范围是两个集合的并集——这很适合"apps 目录整体构建 + 变更引发的影响面"同时覆盖的场景。对应 Langfuse:
turbo run build --filter=...[HEAD^1] --filter=./packages/*即"自上次提交变更的包及其被依赖方,外加 packages/ 下所有共享包"。
变更集内、但排除特定包
turbo run build --filter=[main...HEAD] --filter=!docs语义:先取main...HEAD的变更包(仅本身,不带被依赖方),再从结果中排除docs。在 Langfuse 中类似地:
# 自 main 以来有变更的包,排除 worker turbo run build --filter=[main...HEAD] --filter=!worker过滤器之间的求值规则:多个正向过滤器取并集,!否定再从中扣减。把"变更集""目录""scope""方向"组合使用,可以表达相当精细的执行范围。
调试过滤器:--dry与--dry=json
过滤器写复杂之后,最重要的是先确认"到底会跑哪些包"。Turbo 提供了只做规划、不实际执行的 dry-run 模式:
# 只打印将要执行的命令,不真正运行 turbo run build --filter=web... --dry # 输出机器可读的 JSON 规划 turbo run build --filter=...[HEAD^1] --dry=json使用建议:
--dry:人眼查看任务清单、执行顺序与缓存命中计划,是验证过滤器语义最直接的手段。--dry=json:供脚本解析,CI 里可以据此断言"变更只影响了预期的包"。- 配合 ci-runtime-analyst.md 中的做法:改动
turbo.json或过滤器后,先跑npx turbo run build --dry-run确认影响面,再提交。
此外,turbo.json 中lint、typecheck等任务配置了"outputLogs": "errors-only",dry-run 同样适用于它们,方便在不刷屏的前提下验证范围。
CI/CD 中的过滤模式
文档把 CI 场景单独归纳,这在 Langfuse 的 .github/workflows 流水线中都能找到对应实现。
PR 校验:最常用的一条命令
turbo run build test lint --affected这是文档强调的"最高效的 CI 设置":只在真正变更的包(及被依赖方)上跑构建、测试与 lint,而不是每次 PR 全量执行。同目录参考 RULE.md 也给出了等价的 YAML 片段:
# .github/workflows/ci.yml - run: turbo run build test lint --affected只部署变更的应用
turbo run deploy --filter=./apps/* --filter=[main...HEAD]语义:apps 下所有应用 ∪ 自main以来有变更的包,取并集后再由deploy任务在选中范围内执行。对应 Langfuse:
turbo run start --filter=./web --filter=./worker --filter=[main...HEAD]指定应用及其依赖的完整重建
turbo run build --filter=production-app...对应 Langfuse:
# 完整重建 worker 及其依赖链(@langfuse/shared、@repo/langfuse-skills 等) turbo run build --filter=worker...这一写法在仓库 CI 中确实以pnpm 过滤的形式出现(见 pipeline.yml 的tests-workerjob):
pnpm --filter=worker... run build值得注意的细节是:pnpm --filter=worker... run build与pnpm turbo run build --filter=worker...是两个不同工具的过滤。前者是 pnpm 自身的--filter(按 workspace 依赖图选中 worker 及其依赖,直接在对应目录执行build脚本);后者把--filter传给 turbo,由 turbo 结合 turbo.json 的任务图统一编排并走缓存。Langfuse 的 CI 中两者都在用:pnpm --filter=worker... run build用于快速准备测试所需的构建产物,而pnpm turbo run build:test(如 warm-caches.yml)则走完整的 turbo 管线。理解这一区别,有助于在阅读和编写 Langfuse 的 CI 工作流时不混淆二者的语义。
只构建依赖、跳过目标应用
前文提到过 pipeline.yml 中的pnpm turbo run build --filter=web^...("只构建 web 的依赖,不构建 web 本身")。在 CI 里这类写法用于"提前热缓存依赖产物、把目标应用留到专门的 job 构建",是^排除语义的高价值应用。
过滤与任务编排的配合:Langfuse 的真实任务图
过滤器决定"在哪些包上跑",而dependsOn决定"每个包内任务何时能跑"。Langfuse 的 turbo.json 展示了二者如何协同:
{ "tasks": { "build": { "dependsOn": ["db:generate", "^build"], "outputs": ["dist/**", ".next/**", "!.next/cache/**"], "cache": true }, "test": { "dependsOn": ["^test", "db:generate"], "cache": true }, "lint": { "dependsOn": ["@repo/eslint-plugin#build", "^build"], "cache": true } } }从中可以读出两个与过滤强相关的实践要点:
^build与dependsOn的配合:即使你只运行turbo run build --filter=web,dependsOn: ["^build"]也要求 web 的依赖(如@langfuse/shared)先构建。但注意:dependsOn的^build只在"被选中的包范围内"生效。若你想在构建web时连带构建其依赖,就必须用--filter=web...把依赖包也选进执行范围——这正是过滤与任务图必须一起理解的原因。- 跨包任务引用:
lint任务依赖@repo/eslint-plugin#build,即 lint 前必须先构建 eslint 插件。过滤器可以用同样的pkg#task记号精确指定某个包的某个任务:
# 只跑 eslint-plugin 的 build 任务 turbo run @repo/eslint-plugin#build # 同时指定多个包的特定任务 turbo run web#build @langfuse/shared#testpackage#task记号与--filter互补:前者在任务名层面精确定位,后者在包集合层面批量圈选。
常见误用与最佳实践小结
结合本技能目录的 SKILL.md 与 RULE.md,归纳与过滤相关的几条纪律:
- 代码与 CI 中始终写
turbo run:package.json脚本与 CI 工作流用turbo run build --affected,turbo build简写只用于交互式终端。Langfuse 根 package.json 全部遵循"turbo run <task>"写法。 - 根脚本只做委托:根
package.json的脚本只转发给turbo run(必要时附加--filter,如dev:web),不要在里面直接执行任务逻辑,否则会绕过 turbo 的并行与缓存。 - 不要用
&&串联 turbo 任务:把编排交给dependsOn与过滤器,而不是脚本链。 - 变更集默认带被依赖方:除非有明确理由,PR 校验用
--affected或...[ref],因为共享包变更必须回归消费方。 - 先
--dry后执行:任何复杂组合(如--filter=...[HEAD^1] --filter=./packages/* --filter=!worker)都先用--dry验证范围,再放进 CI。 - 区分 pnpm
--filter与 turbo--filter:在 Langfuse 的 CI 文件里两者并存,语义不同、各自服务不同目的,阅读时注意命令前缀是pnpm --filter=还是pnpm turbo run ... --filter=。
速查表:按目标选命令
| 目标 | 命令 |
|---|---|
| 单包跑任务 | turbo run build --filter=web |
| 包 + 其全部依赖 | turbo run build --filter=web... |
| 仅依赖、排除包本身 | turbo run build --filter=web^...(pipeline.yml 真实用例) |
| 包 + 全部被依赖方 | turbo run test --filter=...@langfuse/shared |
| 仅被依赖方、排除包本身 | turbo run test --filter=...^@langfuse/shared |
| 自上次提交以来的变更包(仅本身) | turbo run lint --filter=[HEAD^1] |
| 自某个分支点以来的变更包 | turbo run lint --filter=[main...HEAD] |
| 变更包 + 被依赖方(PR 校验,首选) | turbo run build test lint --affected |
| 变更包 + 被依赖方(手动指定 ref) | turbo run build --filter=...[HEAD^1] |
| 目录过滤 | turbo run build --filter=./packages/* |
| Scope 过滤 | turbo run build --filter=@langfuse/* |
| 排除 | turbo run build --filter=./packages/* --filter=!worker |
| 复杂组合(变更集 ∪ 目录 − 排除) | turbo run build --filter=[main...HEAD] --filter=./packages/* --filter=!ee |
| 只查看执行计划 | turbo run build --filter=web... --dry/--dry=json |
| 只部署/启动变更的应用 | turbo run start --filter=./web --filter=./worker --filter=[main...HEAD] |
以上模式与命令全部可以直接在 Langfuse 仓库中复现:本地开发按需使用 package.json 中已封装的pnpm run dev:web(即turbo run dev --filter=web)、pnpm run dev:worker等脚本;CI 层面则参考 pipeline.yml 与 warm-caches.yml 中turbo run build:test、turbo run build --filter=web^...、pnpm --filter=worker... run build的真实用法。理解并善用过滤,是把 Langfuse 这类多包仓库的构建与测试成本控制在"最小必要范围"的关键能力。
【免费下载链接】langfuse🪢 Open source AI engineering platform: LLM evals, observability, metrics, prompt management, playground, datasets. Integrates with OpenTelemetry, LangChain, OpenAI SDK, LiteLLM, and more. 🍊YC W23项目地址: https://gitcode.com/GitHub_Trending/la/langfuse
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考