effect-cli
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseStructure
结构
Command.run(command, { name, version })
├── Command (parent)
│ ├── Options (named flags: --verbose, --depth 5)
│ ├── Args (positional: <repo> <path>)
│ └── Command.withSubcommands([...])
│ ├── Command (child — can yield* parent for its config)
│ └── Command (child)Command.run(command, { name, version })
├── Command (parent)
│ ├── Options (named flags: --verbose, --depth 5)
│ ├── Args (positional: <repo> <path>)
│ └── Command.withSubcommands([...])
│ ├── Command (child — can yield* parent for its config)
│ └── Command (child)Minimal App
最简应用
ts
import { Command, Options, Args } from "@effect/cli"
import { NodeContext, NodeRuntime } from "@effect/platform-node"
import { Console, Effect } from "effect"
const greet = Command.make(
"greet",
{
name: Args.text({ name: "name" }),
loud: Options.boolean("loud").pipe(Options.withAlias("l"))
},
({ name, loud }) =>
Console.log(loud ? name.toUpperCase() + "!" : `Hello, ${name}!`)
)
const cli = Command.run(greet, { name: "Greeter", version: "1.0.0" })
Effect.suspend(() => cli(process.argv)).pipe(
Effect.provide(NodeContext.layer),
NodeRuntime.runMain
)ts
import { Command, Options, Args } from "@effect/cli"
import { NodeContext, NodeRuntime } from "@effect/platform-node"
import { Console, Effect } from "effect"
const greet = Command.make(
"greet",
{
name: Args.text({ name: "name" }),
loud: Options.boolean("loud").pipe(Options.withAlias("l"))
},
({ name, loud }) =>
Console.log(loud ? name.toUpperCase() + "!" : `Hello, ${name}!`)
)
const cli = Command.run(greet, { name: "Greeter", version: "1.0.0" })
Effect.suspend(() => cli(process.argv)).pipe(
Effect.provide(NodeContext.layer),
NodeRuntime.runMain
)Commands
命令
ts
// No config
const root = Command.make("app")
// With config (record of Options + Args, arbitrarily nested)
const cmd = Command.make("cmd", {
verbose: Options.boolean("verbose"),
file: Args.text({ name: "file" })
}, ({ verbose, file }) => Console.log(file))
// Attach handler later
const cmd2 = Command.make("cmd", { n: Args.integer({ name: "n" }) }).pipe(
Command.withHandler(({ n }) => Console.log(n))
)
// Description
const cmd3 = cmd.pipe(Command.withDescription("Does something useful"))ts
// No config
const root = Command.make("app")
// With config (record of Options + Args, arbitrarily nested)
const cmd = Command.make("cmd", {
verbose: Options.boolean("verbose"),
file: Args.text({ name: "file" })
}, ({ verbose, file }) => Console.log(file))
// Attach handler later
const cmd2 = Command.make("cmd", { n: Args.integer({ name: "n" }) }).pipe(
Command.withHandler(({ n }) => Console.log(n))
)
// Description
const cmd3 = cmd.pipe(Command.withDescription("Does something useful"))Subcommands
子命令
ts
const git = Command.make("git", {
verbose: Options.boolean("verbose").pipe(Options.withAlias("v"))
})
const clone = Command.make("clone", { repo: Args.text({ name: "repo" }) },
({ repo }) =>
Effect.gen(function* () {
const { verbose } = yield* git // access parent's parsed config
yield* Console.log(`Cloning ${repo}, verbose=${verbose}`)
})
)
const add = Command.make("add", { path: Args.text({ name: "path" }) },
({ path }) => Console.log(`Adding ${path}`)
)
const command = git.pipe(Command.withSubcommands([clone, add]))Key pattern: subcommand handlers to access the parent's parsed config. This works because extends .
yield* parentCommandCommandEffectts
const git = Command.make("git", {
verbose: Options.boolean("verbose").pipe(Options.withAlias("v"))
})
const clone = Command.make("clone", { repo: Args.text({ name: "repo" }) },
({ repo }) =>
Effect.gen(function* () {
const { verbose } = yield* git // access parent's parsed config
yield* Console.log(`Cloning ${repo}, verbose=${verbose}`)
})
)
const add = Command.make("add", { path: Args.text({ name: "path" }) },
({ path }) => Console.log(`Adding ${path}`)
)
const command = git.pipe(Command.withSubcommands([clone, add]))核心模式:子命令处理器通过来访问父命令解析后的配置。这是可行的,因为继承自。
yield* parentCommandCommandEffectProviding Services
提供服务
ts
cmd.pipe(Command.provide(MyService.layer))
cmd.pipe(Command.provide((config) => MyService.layer(config.path))) // config-dependent
cmd.pipe(Command.provideEffect(MyService, (_config) => Effect.succeed(impl)))
cmd.pipe(Command.provideSync(MyService, impl))
cmd.pipe(Command.provideEffectDiscard((_config) => Effect.log("Starting...")))
// Transform handler
cmd.pipe(Command.transformHandler((effect, config) =>
Effect.provideService(effect, MyService, impl)
))ts
cmd.pipe(Command.provide(MyService.layer))
cmd.pipe(Command.provide((config) => MyService.layer(config.path))) // config-dependent
cmd.pipe(Command.provideEffect(MyService, (_config) => Effect.succeed(impl)))
cmd.pipe(Command.provideSync(MyService, impl))
cmd.pipe(Command.provideEffectDiscard((_config) => Effect.log("Starting...")))
// Transform handler
cmd.pipe(Command.transformHandler((effect, config) =>
Effect.provideService(effect, MyService, impl)
))Options (Named Flags)
选项(命名标记)
For detailed options reference see effect-cli-options.md.
| Constructor | CLI syntax | Type |
|---|---|---|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
如需详细的选项参考,请查看effect-cli-options.md。
| 构造函数 | CLI语法 | 类型 |
|---|---|---|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
Common Combinators
常用组合子
ts
Options.text("name").pipe(
Options.withAlias("n"), // -n shorthand
Options.withDescription("Your name"),
Options.optional, // Option<string>
// or: Options.withDefault("world"), // string with default
)
// Repetition
Options.text("tag").pipe(Options.repeated) // Array<string>
Options.text("tag").pipe(Options.atLeast(1)) // NonEmptyArray<string>
// Schema validation
Options.text("balance").pipe(Options.withSchema(Schema.BigDecimal))
// Fallback to env var (via Effect Config)
Options.integer("port").pipe(Options.withFallbackConfig(Config.integer("PORT")))
// Fallback to interactive prompt
Options.text("name").pipe(
Options.withFallbackPrompt(Prompt.text({ message: "Enter name:" }))
)ts
Options.text("name").pipe(
Options.withAlias("n"), // -n 简写
Options.withDescription("Your name"),
Options.optional, // Option<string>
// or: Options.withDefault("world"), // 带默认值的string
)
// 重复选项
Options.text("tag").pipe(Options.repeated) // Array<string>
Options.text("tag").pipe(Options.atLeast(1)) // NonEmptyArray<string>
// Schema验证
Options.text("balance").pipe(Options.withSchema(Schema.BigDecimal))
// 回退到环境变量(通过Effect Config)
Options.integer("port").pipe(Options.withFallbackConfig(Config.integer("PORT")))
// 回退到交互式提示
Options.text("name").pipe(
Options.withFallbackPrompt(Prompt.text({ message: "Enter name:" }))
)Args (Positional Arguments)
参数(位置参数)
For detailed args reference see effect-cli-args.md.
| Constructor | Type |
|---|---|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
如需详细的参数参考,请查看effect-cli-args.md。
| 构造函数 | 类型 |
|---|---|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
Common Combinators
常用组合子
ts
Args.text({ name: "dir" }).pipe(
Args.optional, // Option<string>
// or: Args.withDefault("/"), // string with default
// or: Args.repeated, // Array<string>
// or: Args.atLeast(1), // NonEmptyArray<string>
)
Args.text({ name: "n" }).pipe(Args.withSchema(Schema.NumberFromString))
Args.text({ name: "r" }).pipe(Args.withFallbackConfig(Config.string("REPO")))
Args.text({ name: "r" }).pipe(Args.withDescription("The repository URL"))ts
Args.text({ name: "dir" }).pipe(
Args.optional, // Option<string>
// or: Args.withDefault("/"), // 带默认值的string
// or: Args.repeated, // Array<string>
// or: Args.atLeast(1), // NonEmptyArray<string>
)
Args.text({ name: "n" }).pipe(Args.withSchema(Schema.NumberFromString))
Args.text({ name: "r" }).pipe(Args.withFallbackConfig(Config.string("REPO")))
Args.text({ name: "r" }).pipe(Args.withDescription("The repository URL"))Prompts
交互式提示
For detailed prompt reference see effect-cli-prompt.md.
ts
import { Prompt } from "@effect/cli"
Prompt.text({ message: "Name:", default: "Alice" }) // string
Prompt.password({ message: "Password:" }) // Redacted
Prompt.integer({ message: "Age:", min: 0, max: 150 }) // number
Prompt.confirm({ message: "Sure?" }) // boolean
Prompt.toggle({ message: "Enable?", active: "on", inactive: "off" }) // boolean
Prompt.select({
message: "Pick env:",
choices: [
{ title: "Production", value: "prod" },
{ title: "Staging", value: "staging" },
{ title: "Dev", value: "dev" }
]
}) // string
Prompt.list({ message: "Tags:", delimiter: "," }) // Array<string>
// Combine prompts
Prompt.all({ name: namePrompt, age: agePrompt })如需详细的提示参考,请查看effect-cli-prompt.md。
ts
import { Prompt } from "@effect/cli"
Prompt.text({ message: "Name:", default: "Alice" }) // string
Prompt.password({ message: "Password:" }) // Redacted
Prompt.integer({ message: "Age:", min: 0, max: 150 }) // number
Prompt.confirm({ message: "Sure?" }) // boolean
Prompt.toggle({ message: "Enable?", active: "on", inactive: "off" }) // boolean
Prompt.select({
message: "Pick env:",
choices: [
{ title: "Production", value: "prod" },
{ title: "Staging", value: "staging" },
{ title: "Dev", value: "dev" }
]
}) // string
Prompt.list({ message: "Tags:", delimiter: "," }) // Array<string>
// 组合提示
Prompt.all({ name: namePrompt, age: agePrompt })Prompt-Based Commands
基于提示的命令
ts
const favorites = Command.prompt(
"favorites",
Prompt.all([
Prompt.select({ message: "Color?", choices: [...] }),
Prompt.confirm({ message: "Continue?" })
]),
([color, confirmed]) => Console.log(`Color: ${color}`)
)ts
const favorites = Command.prompt(
"favorites",
Prompt.all([
Prompt.select({ message: "Color?", choices: [...] }),
Prompt.confirm({ message: "Continue?" })
]),
([color, confirmed]) => Console.log(`Color: ${color}`)
)Running
运行
ts
const cli = Command.run(command, {
name: "My App",
version: "1.0.0",
// summary: Span.text("Short summary"),
// footer: HelpDoc.p("Footer text"),
})
// cli: (args: ReadonlyArray<string>) => Effect<void, ...>
Effect.suspend(() => cli(process.argv)).pipe(
Effect.provide(NodeContext.layer),
NodeRuntime.runMain
)process.argvts
const cli = Command.run(command, {
name: "My App",
version: "1.0.0",
// summary: Span.text("Short summary"),
// footer: HelpDoc.p("Footer text"),
})
// cli: (args: ReadonlyArray<string>) => Effect<void, ...>
Effect.suspend(() => cli(process.argv)).pipe(
Effect.provide(NodeContext.layer),
NodeRuntime.runMain
)process.argvBuilt-In Options (Automatic)
内置选项(自动生成)
Every CLI app gets these for free:
| Flag | Effect |
|---|---|
| Print auto-generated help |
| Print version |
| Interactive wizard for all options/args |
| Shell completion scripts |
| Set log level |
每个CLI应用都会自动获得以下选项:
| 标记 | 作用 |
|---|---|
| 打印自动生成的帮助信息 |
| 打印版本号 |
| 针对所有选项/参数的交互式向导 |
| 生成Shell补全脚本 |
| 设置日志级别 |
Config File Support
配置文件支持
ts
import { ConfigFile } from "@effect/cli"
Effect.suspend(() => cli(process.argv)).pipe(
Effect.provide(
Layer.mergeAll(
NodeContext.layer,
ConfigFile.layer("myapp") // reads myapp.json, myapp.yaml, etc.
)
),
NodeRuntime.runMain
)Options using will read from the config file.
withFallbackConfigts
import { ConfigFile } from "@effect/cli"
Effect.suspend(() => cli(process.argv)).pipe(
Effect.provide(
Layer.mergeAll(
NodeContext.layer,
ConfigFile.layer("myapp") // 读取myapp.json、myapp.yaml等文件
)
),
NodeRuntime.runMain
)使用的选项会从配置文件中读取值。
withFallbackConfigConfig Precedence
配置优先级
CLI args > (env/config file) > (interactive) > (static)
withFallbackConfigwithFallbackPromptwithDefaultCLI参数 > (环境变量/配置文件) > (交互式) > (静态默认值)
withFallbackConfigwithFallbackPromptwithDefaultAdditional Resources
额外资源
Detailed Options reference
Detailed Args reference
Detailed Prompt reference
详细选项参考
详细参数参考
详细提示参考