如何用 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,升级后请自己重新验证。