Repository navigation
Description
Thanks for #7573. Help, version and error output now follow NO_COLOR exactly as the spec describes. We noticed that Prompt and the command wizard don't consult it, and we aren't sure whether that's intended.
What we observed
With NO_COLOR=1 on a terminal, --help prints no escape sequences. In the same program and the same terminal, a Prompt and the --wizard flow both write colour and style codes. Even with every colour slot of Prompt.Theme set to "", a prompt still writes bold, underline and a reset after every styled fragment.
Environment: [email protected] (tag [email protected], ad61db8) and @effect/[email protected], on Node 26.10.0 and macOS. From reading the source, the same lines are unchanged on main at 8b0eac6 (packages/effect/src/cli/internal/ansi.ts:108-111, packages/effect/src/cli/internal/wizard.ts:250-270).
Reproduction
A Prompt.Select with an all-empty-colour theme, prompt.ts:
import * as NodeRuntime from "@effect/platform-node/NodeRuntime"
import * as NodeServices from "@effect/platform-node/NodeServices"
import { Console, Effect } from "effect"
import { Prompt } from "effect/cli"
// Every colour slot of the theme emptied, on top of NO_COLOR=1.
const noColours = Prompt.makeTheme({
primaryColor: "",
mutedColor: "",
successColor: "",
errorColor: "",
submittedColor: ""
})
const pick = Prompt.Select({
message: "Pick one",
choices: [{ title: "a", value: "a" }, { title: "b", value: "b" }]
})
Prompt.run(pick).pipe(
Effect.flatMap((value) => Console.log(`selected: ${value}`)),
Effect.provideService(Prompt.Theme, noColours),
Effect.provide(NodeServices.layer),
NodeRuntime.runMain
)A one-flag command for --help and --wizard, wizard.ts:
import * as NodeRuntime from "@effect/platform-node/NodeRuntime"
import * as NodeServices from "@effect/platform-node/NodeServices"
import { Effect } from "effect"
import { Command, Flag } from "effect/cli"
const app = Command.make("app", { name: Flag.String("name") })
Command.run(app, { version: "0.0.0" }).pipe(Effect.provide(NodeServices.layer), NodeRuntime.runMain)We ran each under a pseudo-terminal with NO_COLOR=1 and captured the raw output live (escape bytes shown escaped; long lines trimmed with …).
The control, NO_COLOR=1 node wizard.ts --help, has no escapes:
USAGE\r\n app [flags]\r\n\r\nFLAGS\r\n --name string \r\n\r\nGLOBAL FLAGS\r\n --help, -h …
NO_COLOR=1 node prompt.ts, then Enter:
\x1b[?25l?\x1b[0m \x1b[1mPick one\x1b[0m ›\x1b[0m \r\n❯\x1b[0m \x1b[4ma\x1b[0m \r\n b \x1b[2K\x1b[1G\x1b[2K\x1b[G✔\x1b[0m \x1b[1mPick one\x1b[0m …\x1b[0m a\x1b[0m\r\n\x1b[?25hselected: a\r\n
NO_COLOR=1 node wizard.ts --wizard, then Ctrl-C:
\x1b[1m\x1b[96mapp\x1b[0m \x1b[37mv0.0.0\x1b[0m \x1b[1m\x1b[37m· Command wizard\x1b[0m\r\n\x1b[90mBuild a command interactively. Press Ctrl+C to cancel.\x1b[0m\r\n\r\n\x1b[1m\x1b[37mCurrent command\x1b[0m\r\n\x1b[35m $ app\x1b[0m\r\n\r\n\x1b[1m\x1b[96mROOT\x1b[0m \x1b[90m·\x1b[0m \x1b[1m\x1b[37mFLAGS\x1b[0m\r\n\x1b[96m?\x1b[0m \x1b[1mName (--name)\x1b[0m \x1b[90m›\x1b[0m \x07\x1b[?25h\r\n\x1b[31mWizard cancelled.\x1b[0m\r\n
In the prompt output, each \x1b[0m is a reset with nothing to reset once the theme is empty. \x1b[1m is bold and \x1b[4m is underline. The wizard's headings, command block and "cancelled" line use fixed colours (\x1b[96m, \x1b[37m, \x1b[35m, \x1b[31m).
The cursor codes (\x1b[?25l/\x1b[?25h, \x1b[2K, \x1b[1G) are a separate matter. NO_COLOR is about colour, and a prompt needs cursor movement to redraw, so we're not suggesting those change.
Where it happens
ansi.ts:108-111:annotatealways appendsreset, even when every style passed to it is empty.Prompt.ts:623-641: the theme's colours are raw escape strings, and the prompt renderers addAnsi.boldandAnsi.underlinedindependently of the theme.wizard.ts:250-270: the section headings, introduction and "Wizard cancelled." line are coloured unconditionally, andrenderCommandBlockjust below follows the same pattern.- By contrast,
CliOutput.ts:308-319:defaultFormatterchecks the TTY andNO_COLOR, which is why the help control above is clean.
Is this intended?
Maybe prompts are exempt on purpose, since they only run on a terminal anyway. If not, one possible direction is for annotate to skip the reset when no style was applied, and for the prompt renderers and wizard to take styling from the theme, or from the same colour decision the formatter makes. That would let an application follow NO_COLOR with a plain theme. #7556 and its PR #8350 propose an injectable colour decision for CliOutput and leave Prompt out of scope; this may be the other half of that.
Related
unstable/cli:defaultFormatterignoresNO_COLORunless its value is exactly "1" #7555 / Fix CLI formatter NO_COLOR handling #7573 (closed):defaultFormatter's NO_COLOR check.unstable/cli: opt-in terminal capability detection as an injectable service #7556 (open) and PR Add opt-in terminal capability detection for the CLI #8350: opt-in terminal capability detection for the CLI.
Activity