如何用 effect/cli 的 Prompt 做交互式表单?
我在伪终端里依次输入名字、用方向键选环境、按回车确认,程序最后打印出 {"name":"bob","env":"production","confirm":true},三个答案已经是一个带类型的对象,没有手写任何读 stdin 的代码。
做法是:用 Prompt.String、Prompt.Select、Prompt.Confirm 声明每个提问,用 Prompt.all 把它们组合成一个表单,再用 Prompt.run 执行,执行需要 Terminal 服务,在 Node 下由 NodeServices.layer 提供。
Prompt 属于 effect/cli,和 Command、Flag 一样标注为 @stability unstable,小版本之间可能变,所以我建议只在入口层使用。如果你想了解它在整个命令行里的位置,见用 effect/cli 怎么写一个带类型校验的命令行?。下面几个问题,是做一个独立表单时会依次碰到的。
一个最小的交互表单怎么写?
import { NodeRuntime, NodeServices } from "@effect/platform-node"
import { Console, Effect } from "effect"
import { Prompt } from "effect/cli"
const form = Prompt.all({
name: Prompt.String({ message: "Name" }),
env: Prompt.Select({
message: "Environment",
choices: [
{ title: "staging", value: "staging" as const },
{ title: "production", value: "production" as const }
]
}),
confirm: Prompt.Confirm({ message: "Deploy?", initial: true })
})
Prompt.run(form).pipe(
Effect.flatMap((answers) => Console.log(JSON.stringify(answers))),
Effect.provide(NodeServices.layer),
NodeRuntime.runMain
)Prompt.all 接受一个对象,每个字段是一个提问,返回的 Prompt 的结果就是同形状的对象。Prompt.Select 的 choices 里每一项有 title 和 value;value 加上 as const 后,结果类型是字面量联合,而不是宽泛的 string。Prompt.Confirm 的 initial: true 指定默认答案。
Prompt.run(form) 返回一个 Effect,需要 Terminal 服务,所以最后用 Effect.provide(NodeServices.layer) 提供它,再交给 NodeRuntime.runMain。
除了这三种,还有哪些提问类型?
除了 String、Select、Confirm,还有 Int、Number、Password、Hidden、MultiSelect、AutoComplete、Toggle、Date、File、List 等。这些我只在类型声明里看到了名字,没有跑过,用法请以它们各自的类型签名为准。
在 CI 里跑,会不会卡在提问上?
独立的 Prompt.run 我只在伪终端里验证过,没有测非 TTY 的行为,所以不要假设它在 CI 里会自己失败退出。
我测过的是另一种用法:给命令的 flag 挂一个兜底提问,Flag.withFallbackPrompt(Prompt.String({ message: "Owner" }))。语义是命令行没给这个 flag 时才询问。在 TTY 下它弹出提问,输入后继续执行;在非 TTY(stdin 为 /dev/null)下直接报 Missing required flag: --owner 并退出码 1,不会让流水线挂起。如果你的交互只是为了补全缺失的参数,用这个形式比独立表单更适合要在 CI 里跑的工具。
版本是 effect 4.0.5。表单示例我在伪终端里实际跑过,输入名字、方向键选择、确认后得到上面的输出;只验证了String、Select、Confirm三种提问。flag 兜底提问在 TTY 与非 TTY 下的行为也跑过;独立Prompt.run在非 TTY 下的行为没有测。effect/cli整体是 unstable,升级后请自己重新验证。