Dagger TypeScript SDK 目录过滤指南:深入解析 DirectoryFilterOpts 与 Directory.filter()
【免费下载链接】daggerAutomation engine to build, test and ship any codebase. Runs locally, in CI, or directly in the cloud项目地址: https://gitcode.com/GitHub_Trending/da/dagger
导读
在 Dagger 中,Directory是构建、测试与交付流水线的核心数据模型之一,而DirectoryFilterOpts是 TypeScript SDK 为Directory.filter()方法提供的选项类型,用于按 glob 模式或 .gitignore 规则生成目录快照的子集。本文以@dagger.io/daggerTypeScript SDK 在 v0.20 版本中导出的DirectoryFilterOpts类型别名为主体,结合该仓库的 TypeScript 客户端生成源码、Go 运行时与核心 schema 实现,系统讲解exclude、include、gitignore三个选项的含义、底层实现与优先级语义,并给出可直接运行的实战示例,帮助你精确控制进入构建上下文的文件集合。
DirectoryFilterOpts:一次看懂类型定义
DirectoryFilterOpts是 Dagger TypeScript SDK 导出的类型别名,其完整定义位于sdk/typescript/src/api/client.gen.ts:
export type DirectoryFilterOpts = { /** * If set, paths matching one of these glob patterns is excluded from the new snapshot. * Example: ["node_modules/", ".git*", ".env"] */ exclude?: string[] /** * If set, only paths matching one of these glob patterns is included in the new snapshot. * Example: (e.g., ["app/", "package.*"]). */ include?: string[] /** * If set, apply .gitignore rules when filtering the directory. */ gitignore?: boolean }从类型结构看,它本质上是一个全部字段均可选(optional)的纯数据对象,被用作Directory.filter(opts?: DirectoryFilterOpts): Directory方法的唯一入参。三个字段的语义如下:
| 字段 | 类型 | 默认行为 | 作用 |
|---|---|---|---|
exclude | string[] | 不排除任何路径 | 命中任一 glob 模式的路径从新快照中移除 |
include | string[] | 包含所有路径 | 仅保留命中任一 glob 模式的路径 |
gitignore | boolean | false | 过滤时应用.gitignore规则 |
对应地,Go 运行时中也有完全一致的结构体DirectoryFilterOpts(见 sdk/typescript/runtime/internal/dagger/dagger.gen.go),字段为Exclude []string、Include []string、Gitignore bool,其生成代码逐一通过querybuilder.IsZeroValue判断后作为可选参数附加到filter查询选择器上。这说明该类型别名是 SDK 各语言代码生成器统一产出的"选项对象"模式,TypeScript 与 Go 的调用方式一一对应。
Directory.filter():从哪里来、返回什么
DirectoryFilterOpts只服务于Directory类上的一个方法。在 sdk/typescript/src/api/client.gen.ts 中,其签名与 JSDoc 注释如下:
/** * Return a snapshot with some paths included or excluded * @param opts.exclude If set, paths matching one of these glob patterns is excluded from the new snapshot. Example: ["node_modules/", ".git*", ".env"] * @param opts.include If set, only paths matching one of these glob patterns is included in the new snapshot. Example: (e.g., ["app/", "package.*"]). * @param opts.gitignore If set, apply .gitignore rules when filtering the directory. */ filter = (opts?: DirectoryFilterOpts): Directory => { const ctx = this._ctx.select("filter", { ...opts }) return new Directory(ctx) }关键特性:
- 返回值仍是
Directory:filter()不会立即执行任何 I/O,而是把filter选择器追加到惰性求值的查询上下文中,返回一个新的Directory实例。因此它可以继续链式调用entries()、file()、dockerBuild()等方法,直到真正触发执行(如entries()、contents()等异步方法)时引擎才落盘计算。 - opts 可省略:不传任何选项时等价于"什么都不过滤",返回原目录快照。
- 惰性语义:与 Dagger 整体设计一致,
Directory是快照的抽象句柄,filter只是定义了一个新的快照变换,最终由引擎缓存并执行。
三个选项的语义详解与实战
exclude:剔除不需要的路径
exclude接收一组 glob 模式,命中任一模式的路径会被从新快照中排除。文档中给出的典型示例是排除依赖目录、隐藏文件与环境变量文件:
import { connect } from "@dagger.io/dagger" const filtered = client.host().directory(".") .filter({ exclude: ["node_modules/", ".git*", ".env"], })在上述示例中:
node_modules/:目录后缀带/,表示匹配该目录(及其整棵子树);.git*:匹配.git、.gitignore、.gitattributes等以.git开头的隐藏条目;.env:精确匹配环境变量文件本身。
从实现上看,exclude既可命中文件也可命中目录:仓库集成测试 core/integration/directory_test.go 中的TestDirectoryFilterIncludeExclude验证了Exclude: ["subdir"]可以整体剔除一个子目录,也验证了Exclude: ["*.rar"]可以按后缀过滤散落在各层级的文件。
include:白名单式保留
include接收一组 glob 模式,只有命中任一模式的路径会被保留,其余全部丢弃。文档示例:
const filtered = client.host().directory(".") .filter({ include: ["app/", "package.*"], })该示例只保留app/目录与package.json、package-lock.json这类以package.开头的文件,非常适合在打包场景下构造"最小构建上下文"。
测试同样验证了 include 的匹配行为:对一个包含a.txt、b.txt、c.txt.rar及subdir/的目录执行Include: ["*.rar"],最终entries()只返回["c.txt.rar"]。
gitignore:复用仓库忽略规则
gitignore是一个布尔开关。置为true时,Dagger 会在过滤时应用目标目录内的.gitignore规则:
const filtered = client.host().directory(".") .filter({ gitignore: true })测试用例构造了内容为b.txt\nsubdir/\n的.gitignore文件后调用Filter(dagger.DirectoryFilterOpts{Gitignore: true}),结果中b.txt与subdir/被剔除,而.gitignore自身、a.txt、c.txt.rar得以保留——即.gitignore不会"忽略自己"。
gitignore适合直接接住仓库既有的忽略配置,避免在流水线里重复维护一套 glob 白名单。它也可以与exclude、include组合使用(组合优先级见下一节)。
include 与 exclude 的优先级语义(由测试确认)
当include与exclude同时出现时,二者并非简单的"取并集"。仓库集成测试TestDirectoryFilterIncludeExclude明确验证了如下规则:
- exclude 优先于 include:
Include: ["*.txt"]且Exclude: ["b.txt"]时,结果为["a.txt"];反过来Include: ["a.txt"]且Exclude: ["*.txt"]时,结果为[]。 - 即:先按 include 做白名单收缩,再按 exclude 做黑名单剔除;exclude 永远压过 include。
- 过滤作用于目录的每一层:对
subdir执行filter({ exclude: ["*.rar"] })时,其内部的d.txt、e.txt正常保留,f.txt.rar被剔除,说明模式匹配是递归、逐条目进行的。
这一语义与底层CopyFilter的实现一致(见下节),在编写流水线时请牢记:不要指望 include 能"救回"被 exclude 命中的路径。
源码级实现:从 GraphQL 选择器到 CopyFilter
理解DirectoryFilterOpts的底层机制,可以沿两条路径追溯:
路径一:TypeScript → Go 运行时
filter = (opts?) => this._ctx.select("filter", { ...opts })生成一个名为filter的 GraphQL 选择器。在 Go 运行时 sdk/typescript/runtime/internal/dagger/dagger.gen.go 中,对应生成方法为:
func (r *Directory) Filter(opts ...DirectoryFilterOpts) *Directory { q := r.query.Select("filter") // `exclude` 可选参数 if !querybuilder.IsZeroValue(opts[i].Exclude) { q = q.Arg("exclude", opts[i].Exclude) } // `include`、`gitignore` 同理 return &Directory{query: q} }未设置的选项通过零值判断被省略,不会出现在 GraphQL 请求中。
路径二:引擎侧 schema 实现
在 core/schema/directory.go 中,filter选择器对应的参数类型FilterArgs直接内嵌core.CopyFilter,其解析函数把三个参数原样转发给withDirectory选择器:
type FilterArgs struct { core.CopyFilter } func (s *directorySchema) filter(ctx context.Context, parent dagql.ObjectResult[*core.Directory], args FilterArgs) (inst dagql.ObjectResult[*core.Directory], err error) { // 内部等价于:在 scratch 目录上执行 // withDirectory(path="/", source=parent, exclude=..., include=..., gitignore=..., owner="") ... }也就是说,Directory.filter在引擎内部被降级实现为"从空目录(scratch)复制父目录内容,并应用复制过滤规则"。而CopyFilter正是这套规则的数据结构,定义于 core/directory.go:
type CopyFilter struct { Exclude []string `default:"[]"` Include []string `default:"[]"` Gitignore bool `default:"false"` } func (cf *CopyFilter) IsEmpty() bool { return len(cf.Exclude) == 0 && len(cf.Include) == 0 && !cf.Gitignore }它同时被withDirectory、withDirectoryDockerfileCompat等目录复制操作的持久化状态复用(见 core/directory.go 中persistedDirectoryWithDirectoryLazy等结构体的Filter CopyFilter字段),并在 schema 的copyFilterInputs中被序列化为 GraphQL 输入。由此可以推断:
- 过滤规则随目录快照一起被持久化、参与内容寻址与缓存;
- 相同的
exclude/include/gitignore组合在多个流水线节点中可被缓存命中; IsEmpty()用于短路优化——没有任何过滤规则时跳过额外处理。
完整实战示例:为构建上下文瘦身
下面是一个把"过滤 + 构建"串起来的端到端 TypeScript 示例,可用于在 CI 中把仓库目录裁剪后再交给dockerBuild:
import { connect } from "@dagger.io/dagger" connect(async (client) => { // 1. 读取仓库目录 const repo = client.host().directory(".") // 2. 过滤:先按 .gitignore 剔除,再补一刀排除大目录 const buildCtx = repo.filter({ gitignore: true, exclude: ["dist/", "coverage/", "*.tar.gz"], }) // 3. 白名单收紧(可选,与 exclude 组合时 exclude 优先) const minimalCtx = repo.filter({ include: ["src/", "Dockerfile", "package.json", "yarn.lock"], }) // 4. 用过滤后的快照执行构建 const built = buildCtx.dockerBuild().stdout() console.log(await built) })组合建议:
- 只想"少带一点":优先
gitignore: true加少量exclude,与仓库日常忽略规则保持一致; - 需要"严格白名单":用
include精确枚举,同时留意exclude会压过include; - 过滤后再挂载:
filter()返回的Directory也可作为withMountedDirectory、withDirectory的 source 参数,用于把裁剪后的目录挂进容器,从而减小上下文传输与缓存体积。
小结与注意事项
DirectoryFilterOpts是Directory.filter()的选项对象,包含exclude、include、gitignore三个可选字段,其类型定义与 JSDoc 见 sdk/typescript/src/api/client.gen.ts,Go 侧对应结构体见 sdk/typescript/runtime/internal/dagger/dagger.gen.go。filter返回新的惰性Directory,不会立即触发 IO;它内部复用withDirectory+CopyFilter的复制过滤机制(core/schema/directory.go、core/directory.go),过滤规则参与快照持久化与缓存。- 优先级语义:exclude 覆盖 include;模式递归作用于各层目录,由集成测试 core/integration/directory_test.go 的
TestDirectoryFilterIncludeExclude逐条验证。 - 实际匹配行为(目录后缀
/、通配符、.gitignore不忽略自身)可直接参考上述测试用例,在自定义 glob 前先跑一遍测试数据构造,避免踩中模式语义的坑。
【免费下载链接】daggerAutomation engine to build, test and ship any codebase. Runs locally, in CI, or directly in the cloud项目地址: https://gitcode.com/GitHub_Trending/da/dagger
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考