Effect 命令行框架 `Command.withExamples` 实战:为 CLI 命令注入结构化使用示例与 EXAMPLES 帮助输出
2026/9/14 2:21:24 网站建设 项目流程

Effect 命令行框架Command.withExamples实战:为 CLI 命令注入结构化使用示例与 EXAMPLES 帮助输出

【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code

导读

本文聚焦 Effect 类型安全 CLI 框架(effect/unstable/cli模块)在 4.0.0 版本中新增的Command.withExamples组合器,讲解如何为命令附加“具体可执行的命令行示例”,并通过HelpDoc.examples结构化暴露、由默认帮助格式化器渲染为EXAMPLES区块。读者将掌握:Command.Example数据模型的字段语义、withExamples的两种调用方式(pipe 与数据优先)、示例在帮助输出中的渲染规则,以及如何用官方测试用例验证输出格式。

关联变更说明原文位于仓库内的 few-foxes-grin.md,对应实现与测试位于.repos/effect-smol/packages/effect下。

变更背景:CLI 帮助信息的“最后一公里”

effect/unstable/cli是 Effect 提供的类型安全命令行解析框架:命令(Command)由Command.make创建,通过组合器(combinators)挂载参数、选项、子命令、描述与帮助文档。此前,命令的HelpDoc已经能承载描述(description)、用法(usage)、参数(args)、子命令分组(subcommands)与注解(annotations),但缺少“具体用法示例”这一维度——帮助文本只能告诉用户“有哪些参数”,却无法直观展示“这个命令该怎么用”。

本次变更(changelog 中标记为"effect": patch)补齐了这一缺口:新增Command.withExamples,用于将一组可直接执行的命令行调用示例绑定到命令上;这些示例会:

  1. 以结构化形式暴露在HelpDoc.examples字段(类型为ReadonlyArray<ExampleDoc>);
  2. 由默认帮助格式化器(CliOutput)渲染为醒目的EXAMPLES区块。

数据模型:Command.ExampleHelpDoc.ExampleDoc

示例的数据结构定义在 Command.ts 的Command.Example接口中:

export interface Example { readonly command: string readonly description?: string | undefined }

字段语义:

  • command必填。一条完整的命令行调用示例,例如"myapp login --token sbp_abc123"。注意它是一段字符串,框架不会对它做解析校验,完全由作者保证其正确性。
  • description可选。对该示例的补充说明,例如"Log in with a token"。渲染时若存在则显示为# 注释行;缺省时示例命令将紧挨前一项输出。

对应地,帮助文档侧的类型是HelpDoc.ExampleDoc(见 HelpDoc.ts),同样包含command与可选的descriptionHelpDoc结构体上的examples?: ReadonlyArray<ExampleDoc>字段(HelpDoc.ts)即为最终暴露示例的出口。

核心 API:Command.withExamples的两种调用形态

withExamples定义于 Command.ts,采用 Effect 标准的dual风格重载,同时支持数据优先与**数据最后(pipe)**两种调用方式:

export const withExamples: { (examples: ReadonlyArray<Command.Example>): <const Name extends string, Input, E, R, ContextInput>( self: Command<Name, Input, ContextInput, E, R> ) => Command<Name, Input, ContextInput, E, R> <const Name extends string, Input, E, R, ContextInput>( self: Command<Name, Input, ContextInput, E, R>, examples: ReadonlyArray<Command.Example> ): Command<Name, Input, ContextInput, E, R> }

底层实现非常轻量:makeCommand({ ...toImpl(self), examples })——即保留命令原有全部属性,仅替换examples字段。它不会校验命令是否可执行、不会触碰 handler,纯粹是帮助文档层面的元数据增强,与命令的解析行为完全解耦。

在内部实现(internal/command.ts)中,空数组或未提供examples时不会生成HelpDoc.examples字段,仅当examples.length > 0时才注入,避免无意义的空区块。

调用示例(pipe 风格)

以下代码来自Command.withExamples的 JSDoc 内联示例(Command.ts):

import { Command } from "effect/unstable/cli" const login = Command.make("login").pipe( Command.withExamples([ { command: "myapp login", description: "Log in with browser OAuth" }, { command: "myapp login --token sbp_abc123", description: "Log in with a token" } ]) ) login.examples.map((example) => example.command) // => ["myapp login", "myapp login --token sbp_abc123"]

要点:

  • Command.make("login")创建命令后,通过.pipe(Command.withExamples([...]))链式挂载示例;
  • 挂载后可直接通过命令对象的examples属性读取结构化示例数据;
  • withExamples属于@category combinators组合器,@since 4.0.0

组合器链的扩展能力

withExamples不是孤立的——它可与Command.withDescriptionCommand.withSubcommands等组合器自由叠加,构建出信息完整的帮助体系。例如先描述后示例:

const secret = Command.make("secret").pipe( Command.withDescription("Manage secrets"), Command.withExamples([ { command: "myapp secret list", description: "List all secrets" }, { command: "myapp secret set --name api_key --value 'xxx'" } ]) ) const root = Command.make("tool").pipe(Command.withSubcommands([secret]))

渲染规则:默认帮助格式化器的EXAMPLES区块

示例的实际呈现由默认帮助格式化器 CliOutput.ts 负责。当doc.examples存在且非空时,输出按以下规则生成:

EXAMPLES # Log in with browser OAuth myapp login # Log in with a token myapp login --token sbp_abc123

具体渲染逻辑:

  • 区块标题使用colors.bold("EXAMPLES")加粗输出;
  • 遍历每个示例:
    • example.description存在,输出一行# <description>(使用colors.dim弱化显示),并在非首个示例前插入空行做视觉分隔;
    • 命令本身以colors.cyan(example.command)青色输出;
    • 若示例缺少description且前一项有描述,则同样补一个空行,保证命令块之间的层次清晰;
  • previousHadDescription变量用于跟踪“上一项是否有描述”,这是空行逻辑的关键状态。

渲染结果只与commanddescription两个字段相关,与示例在数组中的顺序一致,不排序、不去重——重复示例会原样输出(见下文测试用例)。

测试验证:官方用例如何断言输出

本次变更附带了覆盖测试,位于 Help.test.ts,测试名即为"renders command examples"。它通过Command.runWith+--help驱动真实帮助渲染,并用快照断言输出:

it.effect("renders command examples", () => Effect.gen(function*() { const command = Command.make("login").pipe( Command.withDescription("Authenticate with Supabase"), Command.withExamples([ { command: "myapp login", description: "Log in with browser OAuth" }, { command: "myapp login --token sbp_abc123", description: "Log in with a token" }, { command: "myapp login --logout" }, { command: "myapp login --logout" }, { command: "myapp login", description: "Log in with browser OAuth" } ]) ) const runLogin = Command.runWith(command, { version: "1.0.0" }) yield* runLogin(["--help"]) const output = (yield* TestConsole.logLines).join("\n") expect(output).toMatchInlineSnapshot(`...`) }).pipe(Effect.provide(TestLayer)))

快照断言的关键片段:

DESCRIPTION Authenticate with Supabase USAGE login [flags] GLOBAL FLAGS --help, -h Show help information --version, -v Show version information --wizard Start wizard mode for a command --completions <bash|zsh|fish|sh> Print shell completion script (choices: bash, zsh, fish, sh) --log-level <all|trace|debug|info|warn|warning|error|fatal|none> Sets the minimum log level (choices: all, trace, debug, info, warn, warning, error, fatal, none) EXAMPLES # Log in with browser OAuth myapp login # Log in with a token myapp login --token sbp_abc123 myapp login --logout myapp login --logout # Log in with browser OAuth myapp login

从该测试可以提炼出三条值得注意的渲染事实:

  1. 描述行转注释:带description的示例渲染为# <description>注释行,且上下示例之间以空行分隔;
  2. 无描述的示例连排{ command: "myapp login --logout" }这类无描述示例直接连续输出,前一项有描述时仍会插入一个空行;
  3. 不去重、保顺序:测试特意塞入重复项myapp login --logout(两次)与重复的myapp login,快照中均原样保留——说明withExamples是纯展示层元数据,不承担任何去重/校验职责。

实践要点与适用建议

何时使用withExamples

  • 对外 CLI 工具:面向终端用户,帮助信息即文档,示例能显著降低上手成本;
  • 复杂参数组合:当命令存在多套 flag 组合(如 token 登录 vs 浏览器 OAuth 登录)时,用示例比长篇幅的参数说明更直观;
  • 团队内部脚手架:把“最常用的几条命令”直接写进帮助,减少查阅文档的往返。

注意事项

  • command字段是纯文本,框架不执行、不校验、不补全 shell 前缀——myapp这类二进制名前缀需自行拼写;
  • 示例属于帮助文档元数据,不影响命令解析、参数校验与 handler 执行;
  • 保持示例数量精简,避免EXAMPLES区块喧宾夺主;结合withDescription使用效果最佳;
  • 若希望帮助文本中不出现SUBCOMMANDS区块(如仅有顶层命令的场合),可参考 Help.test.ts 的既有测试逻辑:Command.runWith渲染--help后断言输出不含SUBCOMMANDS<subcommand>字样,按需配合示例区的取舍。

与其他 CLI 帮助特性的组合

HelpDoc的完整信息面包括descriptionusageargssubcommandsannotations与本次新增的examples(HelpDoc.ts)。一个生产级命令的建议配置顺序:

const deploy = Command.make("deploy").pipe( Command.withDescription("Deploy the current project"), Command.withExamples([ { command: "myapp deploy --env production", description: "Deploy to production" }, { command: "myapp deploy --env staging --dry-run", description: "Dry-run staging deploy" } ]) )

总结

Command.withExamples用最少的 API 表面积(一个dual组合器 + 两个字段的数据模型)为 Effect CLI 框架补齐了“帮助信息示例化”能力:作者只需提供{ command, description }数组,框架便会将其结构化注入HelpDoc.examples,并由默认格式化器渲染成EXAMPLES区块。该能力自effect@4.0.0起可用,属于 patch 级增强,与既有的withDescriptionwithSubcommands等组合器完全正交,是构建类型安全、自带良好帮助体验的 CLI 工具链中一个低成本、高回报的拼图。

【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询