Dagger CurrentModule 深度解析:在 TypeScript 模块运行时反射模块自身 API
2026/9/16 22:32:22 网站建设 项目流程

Dagger CurrentModule 深度解析:在 TypeScript 模块运行时反射模块自身 API

【免费下载链接】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

CurrentModule 是 Dagger 暴露给模块函数运行时的"反射式模块 API",它让运行中的模块代码能够查询自身的名称与依赖、访问源码目录与生成代码、读写模块执行时的 scratch 工作目录。本文将基于 Dagger 0.20 版本 TypeScript SDK 的 API 参考文档,结合仓库中的客户端生成源码与引擎实现,逐一拆解 CurrentModule 的每个方法、参数语义与底层调用链,帮助你写出真正了解"我是谁、我在哪"的自省型 Dagger 模块。

一、CurrentModule 是什么

在 TypeScript API 参考文档 中,CurrentModule被定义为:

Reflective module API provided to functions at runtime.

即"在运行时提供给函数的反射式模块 API"。它继承自BaseClient,是一棵惰性求值的查询树根节点——调用其方法不会立即执行,而是构建 Dagger GraphQL 查询,直到最终await时才真正与引擎通信。

从引擎侧看,CurrentModule在核心 GraphQL Schema 中被定义为一个实现了Node接口的对象类型:

"""Reflective module API provided to functions at runtime.""" type CurrentModule implements Node { dependencies: [Module!]! generatedContextDirectory: Directory! generators(include: [String!]): GeneratorGroup! id: ID! name: String! source: Directory! workdir(path: String!, exclude: [String!] = [], include: [String!] = [], gitignore: Boolean = false): Directory! workdirFile(path: String!): File! } scalar CurrentModuleID

该定义完整记录在 core/schema/testdata/base_schema.graphqls 中,是与版本化 API 文档一一对应的"黄金事实源"。

构造函数:仅供内部使用

CurrentModule的构造函数签名为:

new CurrentModule(ctx?, _id?, _name?): CurrentModule
  • ctx?Context,查询上下文;
  • _id?CurrentModuleID,当前模块的唯一标识;
  • _name?string,模块名称。

文档与源码(api/client.gen.ts)都明确标注:"Constructor is used for internal usage only, do not create object from it."(构造函数仅供内部使用,请勿自行创建实例)。在 SDK 生成的 Go 运行时客户端中同样如此(见 dagger.gen.go)。你不需要也不应该new CurrentModule(),正确获取它的方式是调用查询根上的入口方法:

const cur = client.currentModule()

currentModule()在 api/client.gen.ts 中实现,它只是简单地创建了一个绑定到currentModuleGraphQL 选择器的CurrentModule实例。引擎侧入口定义在 core/schema/module.go,最终返回一个包着当前执行模块的*core.CurrentModule对象。

二、身份查询:id() 与 name()

这两个方法回答模块运行时最基本的问题:"我是谁?"

id()

id(): Promise<CurrentModuleID>

返回当前模块在本次会话中的唯一标识符CurrentModuleID。在 TypeScript 客户端中,id是惰性求值的:

id = async (): Promise<ID> => { if (this._id) { return this._id } const ctx = this._ctx.select("id") const response: Awaited<ID> = await ctx.execute() return response }

注意其"短路"优化:如果构造时已经注入了_id,则直接返回而不再发起网络查询(api/client.gen.ts)。由于CurrentModule implements Node,它同时支持通过loadCurrentModuleFromID(id: CurrentModuleID!): CurrentModule!从 ID 重新加载对象(见 base_schema.graphqls)。

name()

name(): Promise<string>

返回"正在执行的模块的名称"(The name of the module being executed in)。引擎侧实现非常直接——它直接读取模块对象的名称字段:

func (s *moduleSchema) currentModuleName(...) (string, error) { return curMod.Module.Self().NameField, nil }

见 core/schema/module.go。也就是说,name()返回的正是dagger.json中配置的模块名。与id()相同,name()也带有_name短路缓存(api/client.gen.ts)。

三、目录与文件访问:source() 与 generatedContextDirectory()

模块代码经常需要读取自身源码或生成代码,这两组方法提供了完整的访问路径。

source()

source(): Directory

返回"加载进引擎的模块源码目录,并叠加了可能已生成的代码"(The directory containing the module's source code loaded into the engine (plus any generated code that may have been created))。

这是理解 Dagger 模块工作方式的关键:source()不是原始的裸源码目录,而是"上下文目录 + 生成代码补丁"的合成结果。引擎侧实现清晰地展示了这一叠加过程(core/schema/module.go):

  1. 取出模块的Source.ValueSourceSubpath(若为空则回退到SourceRootSubpath);
  2. 先选择generatedContextDirectory拿到生成内容的 diff;
  3. 再在ContextDirectory上执行withDirectory(path: "/", source: generatedDiff)将生成代码叠加到根目录;
  4. 最后按srcSubpath定位到模块源码所在的子目录并返回。

因此,当你在模块函数里执行dag.currentModule().source()时,得到的是"能直接看到dagger.json、模块源码以及 SDK 生成文件"的完整视图。

generatedContextDirectory()

generatedContextDirectory(): Directory

返回"在模块源码的上下文目录之上生成的文件与目录"(The generated files and directories made on top of the module source's context directory)。它就是上一步叠加到源码上的那层"生成补丁"本身。引擎实现同样清晰(core/schema/module.go):直接在模块源对象上选择generatedContextDirectory字段。

对比小结

方法返回内容典型用途
source()上下文目录 + 生成代码(合成后)读取模块整体源码、遍历文件
generatedContextDirectory()仅生成的那一层目录查看 SDK 生成了什么、对比生成差异

四、依赖查询:dependencies()

dependencies(): Promise<Module_[]>

返回当前模块的全部依赖模块,每个元素是Module_对象数组。引擎侧遍历模块的依赖树(core/schema/module.go):

depMods := make([]*core.Module, 0, len(mod.Module.Self().Deps.Mods())) for _, dep := range mod.Module.Self().Deps.Mods() { if depInst := dep.ModuleResult(); depInst.Self() != nil { depMods = append(depMods, depInst.Self()) continue } switch dep.(type) { case *CoreMod: // skip 核心内置模块 default: return nil, fmt.Errorf("unexpected mod dependency type %T", dep) } }

从源码可以看到两个实现细节:

  • 跳过CoreMod:Dagger 核心内置模块(如core依赖)不会出现在结果里,返回的是用户声明的模块依赖;
  • 类型校验:遇到无法识别的依赖类型会直接报错,保证返回结构的一致性。

TypeScript 客户端收到结果后,会用selectNode把每个 ID 重新绑定为Module_对象(api/client.gen.ts),因此你可以继续对每个依赖模块做进一步调用。

五、工作目录访问:workdir() 与 workdirFile()

这是CurrentModule中最"动态"的能力:读取模块执行期间对 scratch 工作目录所做的修改。

workdir(path, opts?)

workdir(path: string, opts?: CurrentModuleWorkdirOpts): Directory

Load a directory from the module's scratch working directory, including any changes that may have been made to it during module function execution.

从模块的 scratch 工作目录加载一个目录,包括模块函数执行期间对其做出的任何修改path参数是相对位置,例如"."表示工作目录根。

CurrentModuleWorkdirOpts的三个可选参数(定义于 api/client.gen.ts):

参数类型默认值说明
excludestring[][]排除匹配指定模式的文件/目录,如["node_modules/", ".git*"]
includestring[][]仅包含匹配指定模式的文件/目录,如["app/", "package.*"]
gitignorebooleanfalse是否在目录内应用.gitignore过滤规则

这些过滤模式(exclude/include/gitignore)由引擎透传给host.directory选择器执行(core/schema/module.go),与Directory对象上的过滤语义一致,可以放心复用你熟悉的 glob 模式知识。

引擎侧有一个值得注意的安全校验

if !filepath.IsLocal(args.Path) { return inst, fmt.Errorf("workdir path %q escapes workdir", args.Path) } args.Path = filepath.Join(sdk.RuntimeWorkdirPath, args.Path)

见 core/schema/module.go。它使用filepath.IsLocal拒绝一切可能逃逸出工作目录的路径(如../、绝对路径),然后把合法路径拼接到 SDK 运行时的工作目录前缀下。这意味着workdir()只允许访问工作目录内部,从引擎层面杜绝了路径穿越。

workdirFile(path)

workdirFile(path: string): File

Load a file from the module's scratch working directory, including any changes that may have been made to it during module function execution.

加载工作目录中的单个文件,同样包含执行期间的修改。path示例:"README.md"。其实现与workdir()共享同一套路径校验逻辑(core/schema/module.go),安全性等价。

典型场景:模块函数在容器内生成产物(如构建出的二进制、渲染出的文档),随后用workdirFile("output.json")workdir("dist")把结果读取出来交给调用方。

六、实验性能力:generators()

generators(opts?: CurrentModuleGeneratorsOpts): GeneratorGroup

Return all generators defined by the module(返回模块定义的全部生成器)。

该方法在文档与源码中都被标记为Experimental,引擎侧注解为 "This API is highly experimental and may be removed or replaced entirely."(见 core/schema/module.go 与 base_schema.graphqls),因此在正式生产代码中应谨慎使用,API 可能随时变动。

其唯一参数CurrentModuleGeneratorsOpts

export type CurrentModuleGeneratorsOpts = { /** * Only include generators matching the specified patterns */ include?: string[] }

include用于只返回匹配指定模式的生成器,例如按名称模式筛选。方法返回一个GeneratorGroup对象(api/client.gen.ts),可以继续对其调用进行更细粒度的查询。

七、实战:组合使用 CurrentModule

下面把各方法组合起来,展示一个典型的自省型 TypeScript Dagger 模块函数:

import { dag, Directory, Container } from "@dagger.io/dagger" /** * 自省模块自身:打印名称与依赖,并把"源码+生成代码"与 * "运行期间工作目录中的产物"一起返回给调用方。 */ export function introspectSelf(workdir: Directory): Container { const cur = dag.currentModule() // 1) 身份:模块名(惰性求值,await 时才真正查询) const moduleName = await cur.name() // 2) 依赖:获取模块声明的全部依赖(核心内置模块会被跳过) const deps = await cur.dependencies() console.log(`module=${moduleName}, deps=${deps.length}`) // 3) 源码视图:上下文目录 + 生成代码的合成结果 const sourceWithGenerated = cur.source() // 4) 生成层:只查看 SDK 生成的那一层 const generatedLayer = cur.generatedContextDirectory() // 5) 工作目录:读取函数执行期间产生的文件,支持过滤 const buildOutput = cur.workdir("out", { exclude: ["node_modules/", ".git*"], gitignore: true, }) const manifest = cur.workdirFile("out/manifest.json") // ... 继续用 sourceWithGenerated / buildOutput / manifest 组装容器等 return dag.container() }

要点回顾:

  • name()dependencies()返回 Promise,需要awaitsource()workdir()等返回惰性对象,可先构建查询再统一执行;
  • workdir()只允许工作目录内部路径,../或绝对路径会被引擎拒绝;
  • 需要同时拿到"源码"与"生成代码"时,直接使用source();只想检查生成内容时用generatedContextDirectory()

八、结语

CurrentModule是 Dagger 模块运行时自省的入口:name()/id()回答"我是谁",dependencies()回答"我依赖谁",source()/generatedContextDirectory()回答"我的代码在哪",workdir()/workdirFile()回答"我运行时改了什么"。通过文档、TypeScript 客户端源码(api/client.gen.ts)与引擎实现(core/schema/module.go)三者的对照,你可以准确掌握每个方法的语义与边界,写出更健壮、更动态的 Dagger 模块。

【免费下载链接】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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询