
Effect v4 CLI 全局设置标志直接可 yield解读GlobalFlag.setting与内置标志重命名【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code导读在 Effect v4当前处于 RC 阶段main分支即 v4 开发分支的 unstable CLI 模块中一次关键的 API 演进让全局设置标志global setting flag从仅参与命令解析升级为可直接在处理器中yield*取值的Context.Reference同时将内置全局标志CompletionsFlag、LogLevelFlag重命名为更简洁的Completions、LogLevel。本文以变更集 .repos/effect-smol/.changeset/pre/few-mirrors-pull.md 为核心结合 GlobalFlag.ts 源码、Command.ts 运行器实现与 Command.test.ts 测试用例讲清这一机制的原理、迁移方式与实战用法。读完本文你将掌握如何自定义可yield*的全局设置、如何理解runWith对内置设置如--log-level的双通道处理以及新旧 API 的对应关系。一、变更背景全局标志从命令级声明到可直接取值Effect v4 的 CLI 模块在packages/effect/src/unstable/cli/下其中 GlobalFlag.ts 定义了两种全局标志Action 标志ActionA执行副作用并退出例如--help、--version、--completionsSetting 标志SettingId, A把解析出的值通过 Effect 上下文提供给命令处理器例如--log-level、--config。变更集few-mirrors-pull.md的核心改动是GlobalFlag.settingnow takes{ flag, defaultValue }and returns a setting that is aContext.Reference, so handlers andCommand.provide*effects canyield*global setting values directly.即设置标志本身就是一个Context.Reference服务引用命令处理器可以直接yield* Region拿到解析后的值而无需再通过其他间接方式读取。从 GlobalFlag.ts 的类型定义可以看到这一设计export interface SettingId extends string, A extends Context.ServiceSetting.IdentifierId, A { readonly _tag: Setting readonly id: Id readonly flag: Flag.FlagA }Setting接口直接继承了Context.Service即 Effect 的Context.Reference因此它天然具备可被yield*、可被provideService注入的能力。需要说明变更集描述的是当时合并时的写法{ flag, defaultValue }在当前仓库代码中该构造器已演化为柯里化形式GlobalFlag.setting(id)({ flag })且默认值统一由Flag组合子Flag.optional、Flag.withDefault提供而非由 setting 构造器自身负责详见 CHANGELOG.md。本文以当前仓库源码为准展开。二、当前 API 形态柯里化的GlobalFlag.settingGlobalFlag.ts 中的构造器实现export const setting const Id extends string(id: Id) A(options: { readonly flag: Flag.FlagA }): SettingId, A { settingIdCounter 1 const ref Context.ServiceSetting.IdentifierId, A( effect/unstable/cli/GlobalFlag/${id}/${settingIdCounter} ) return Object.assign(ref, { _tag: Setting as const, id, flag: options.flag }) }要点第一参数id是类型层面的服务标识type-level identifier最终上下文键为effect/unstable/cli/GlobalFlag/${id}见 Setting.Identifier 类型每次调用还附加一个递增计数以保证实例唯一第二参数只接收{ flag }默认值通过Flag.withDefault(...)或Flag.optional表达返回的Setting同时是一个Context.Service因此在Effect.gen中可以直接yield*。2.1 使用示例定义一个可直接 yield 的全局设置参考 Command.test.ts 中的测试模式import * as Command from effect/unstable/cli/Command import * as Flag from effect/unstable/cli/Flag import * as GlobalFlag from effect/unstable/cli/GlobalFlag import * as Option from effect/Option import * as Effect from effect/Effect const Region GlobalFlag.setting(region)({ flag: Flag.string(region).pipe(Flag.optional) }) const deploy Command.make(deploy, {}, () Effect.gen(function*() { const region: Option.Optionstring yield* Region // 直接 yield console.log(region) }) ).pipe(Command.withGlobalFlags([Region])) const run Command.runWith(deploy, { version: 1.0.0 }) // 命令行传入--region us-east-1 → Option.some(us-east-1) // 命令行不传 → Option.none()测试断言验证了两种调用下yield* Region分别得到Option.some(us-east-1)与Option.none()证明处理器中的yield*能直接拿到解析结果。2.2 默认值由 Flag 组合子表达如果不希望值是Option可以改用Flag.withDefault。测试 Command.test.ts 展示了这一用法const Region GlobalFlag.setting(region)({ flag: Flag.string(region).pipe(Flag.withDefault(us-west-2)) }) // 命令行传入--region eu-west-1 → eu-west-1 // 命令行不传 → us-west-2默认值这正是Setting defaults are sourced fromFlagcombinators rather than setting constructor defaults这条行为变更的落地体现——默认值语义完全收敛到Flag层GlobalFlag.setting只负责创建上下文引用。三、runWith如何注入设置值provideService 驱动设置值最终是通过Command.runWith注入到处理器上下文中的。Command.ts 的Provide setting values步骤let program commandImpl.handle(parseResult.success, [command.name]) const logLevel activeFlags.includes(GlobalFlag.LogLevel) ? (yield* GlobalFlag.LogLevel.flag.parse(emptyArgs))[1] : Option.none() program Effect.provideService(program, GlobalFlag.LogLevel, logLevel) for (const flag of activeFlags) { if (flag._tag ! Setting || flag GlobalFlag.LogLevel) continue const [, value] yield* flag.flag.parse(emptyArgs) program Effect.provideService(program, flag, value) } // Apply built-in setting behavior const services Option.match(logLevel, { onNone: () Context.empty(), onSome: (level) Context.make(References.MinimumLogLevel, level) }) yield* Effect.provideContext(program, services)这里揭示了双通道机制对应变更集中 Built-in settings keep internal behavior inrunWith… while also being readable as values值通道--log-level解析出的OptionLogLevel通过Effect.provideService(program, GlobalFlag.LogLevel, logLevel)注入处理器可以yield* GlobalFlag.LogLevel直接读取该值行为通道同一个值又被包装成References.MinimumLogLevel上下文Context.make(References.MinimumLogLevel, level)通过Effect.provideContext生效从而真正改变运行时最小日志级别。换句话说--log-level依旧在底层配置References.MinimumLogLevel但与此同时它也成为可读的上下文值。自定义设置标志则只走第一条通道provideService由使用者自行消费。四、内置全局标志重命名CompletionsFlag→CompletionsLogLevelFlag→LogLevel变更集同时简化了内置标志的命名旧名称新名称对应命令行标志类型GlobalFlag.CompletionsFlagGlobalFlag.Completions--completions bash\|zsh\|fish\|shActionOptionbash \| zsh \| fishGlobalFlag.LogLevelFlagGlobalFlag.LogLevel--log-level all\|trace\|debug\|info\|warn\|warning\|error\|fatal\|noneSettinglog-level, OptionLogLevel在当前源码中这两个内置标志分别定义于 GlobalFlag.ts 与 GlobalFlag.ts。4.1Completions生成 shell 补全脚本export const Completions: ActionOption.Optionbash | zsh | fish action({ flag: Flag.choice(completions, [bash, zsh, fish, sh] as const) .pipe( Flag.optional, Flag.map((v) Option.map(v, (s) s sh ? bash : s)), Flag.withMetavar(bash|zsh|fish|sh), Flag.withDescription(Print shell completion script) ), run: Effect.fnUntraced(function*(shell, { command }) { if (Option.isNone(shell)) return const descriptor CommandDescriptor.fromCommand(command) yield* Console.log( Completions_.generate(command.name, shell.value, descriptor) ) }) })细节接受的取值有bash、zsh、fish、sh其中sh会被规范化为bash脚本生成逻辑委托给 Completions.ts 模块。4.2LogLevel内置日志级别设置export const LogLevel: Settinglog-level, Option.OptionLogLevelType setting(log-level)({ flag: Flag.choiceWithValue( log-level, [ [all, All], [trace, Trace], [debug, Debug], [info, Info], [warn, Warn], [warning, Warn], [error, Error], [fatal, Fatal], [none, None] ] as const ).pipe( Flag.optional, Flag.withDescription(Sets the minimum log level), Flag.withMetavar(all|trace|debug|info|warn|warning|error|fatal|none) ) })注意warning是warn的同义词二者都映射到LogLevel.Warn。该设置除了可被yield* GlobalFlag.LogLevel读取外还会如上一节所述配置References.MinimumLogLevel。4.3 内置标志集合与优先级BuiltIns数组GlobalFlag.ts以默认优先级顺序列出全部内置标志export const BuiltIns: readonly [...] [Help, Version, Wizard, Completions, LogLevel]由于Action 标志按活跃顺序处理、第一个出现的 Action 即退出见 Command.ts 中 first present action wins, then exit 的逻辑该数组顺序直接决定了--help、--version、--wizard、--completions的裁决优先级。Command.runWith会把这组内置标志前置到用户自定义全局标志之前进行收集与解析。五、从测试看行为契约Command.test.ts 为本次变更提供了完整的行为验证直接 yield 设置值L460-L482无默认值时未传参得到Option.none()Flag.withDefault 生效L484-L506未传参得到默认值内置标志可按次运行配置L508-L532可通过CliConfig的builtIns过滤掉GlobalFlag.LogLevel此时--help不再渲染--log-level且传入--log-level debug会被报为 Unrecognized flag而默认配置下--log-level正常存在——这验证了Command.runWith是按CliConfig组装内置标志的Action 与 Setting 混用L534-L569--verboseAction触发副作用并退出--format jsonSetting把值注入处理器作用域与别名冲突L2066-L2119局部标志的短别名可覆盖全局设置-o局部标志也能覆盖另一命令分支声明的全局设置。其中混用测试特别展示了迁移后的直观体验Action 标志run直接执行副作用Setting 标志通过yield*暴露给处理器两者声明在同一Command.withGlobalFlags([...])数组中互不干扰。六、从旧 API 迁移到新 API如果你的代码基于 v4 RC 早期版本可按以下对应关系迁移构造器形态GlobalFlag.setting({ flag, defaultValue })→GlobalFlag.setting(id)({ flag })defaultValue改为在flag上使用Flag.withDefault(value)或Flag.optional读取方式无需再自行维护解析结果缓存直接在Effect.gen内yield*设置引用即可拿到值标志命名GlobalFlag.CompletionsFlag→GlobalFlag.CompletionsGlobalFlag.LogLevelFlag→GlobalFlag.LogLevel注入方式设置值由Command.runWith自动通过provideService注入你也可以在Command.provide*相关的 effect 编排中直接引用这些Context.Reference。七、小结few-mirrors-pull.md这项 patch 把 Effect v4 unstable CLI 的全局设置标志提升为一等公民GlobalFlag.setting返回的Context.Reference让yield*直接取值成为可能--log-level在内置行为References.MinimumLogLevel与可读值两个通道上同时工作内置命名也随之一并简化。若要在自己的项目中继续深入研究可以顺藤摸瓜阅读GlobalFlag.tsAction/Setting模型、构造器与全部内置标志Command.tsrunWith中全局标志收集、Action 优先退出、Setting 注入与MinimumLogLevel应用Command.test.ts覆盖默认值、作用域、别名覆盖与内置标志裁剪的行为测试CHANGELOG.md4.0.0-beta.14 与 4.0.0-beta.13 的完整演进记录。【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考