1/**
2 * Global flags for Effect CLI command trees. Global flags are parsed outside a
3 * single command's local flags and can apply to a command and its descendants.
4 *
5 * This module defines two kinds of global flags: action flags, which run an
6 * effect and stop normal command execution, and setting flags, which provide a
7 * parsed value to the command handler through the Effect context. It also
8 * defines the built-in help, version, wizard, shell-completion, and log-level flags
9 * used by `Command.run` and `Command.runWith`.
10 *
11 * @stability unstable
12 * @since 4.0.0
13 */
14
15import * as Console from "../Console.ts"
16import * as Context from "../Context.ts"
17import * as Effect from "../Effect.ts"
18import type { LogLevel as LogLevelType } from "../LogLevel.ts"
19import * as Option from "../Option.ts"
20import * as CliOutput from "./CliOutput.ts"
21import type * as Command from "./Command.ts"
22import * as Completions_ from "./Completions.ts"
23import * as Flag from "./Flag.ts"
24import * as CommandDescriptor from "./internal/completions/descriptor.ts"
25import * as HelpInternal from "./internal/help.ts"
26
27/* ========================================================================== */
28/* Types */
29/* ========================================================================== */
30
31/**
32 * Context passed to action handlers.
33 *
34 * @stability unstable
35 * @category models
36 * @since 4.0.0
37 */
38export interface HandlerContext {
39 readonly command: Command.Command.Any
40 readonly commandPath: ReadonlyArray<string>
41 readonly version: string
42 readonly builtIns: ReadonlyArray<BuiltIn>
43}
44
45/**
46 * Action flag: side effect + exit (--help, --version, --completions).
47 *
48 * @stability unstable
49 * @category models
50 * @since 4.0.0
51 */
52export interface Action<A> {
53 readonly _tag: "Action"
54 readonly flag: Flag.Flag<A>
55 readonly run: (
56 value: A,
57 context: HandlerContext
58 ) => Effect.Effect<void>
59}
60
61/**
62 * Setting flag: configure command handler's environment (--log-level, --config).
63 *
64 * @stability unstable
65 * @category services
66 * @since 4.0.0
67 */
68export interface Setting<Id extends string, A> extends Context.Service<Setting.Identifier<Id>, A> {
69 readonly _tag: "Setting"
70 readonly id: Id
71 readonly flag: Flag.Flag<A>
72}
73
74/**
75 * Namespace containing type helpers for global setting flags.
76 *
77 * @stability unstable
78 * @since 4.0.0
79 */
80export declare namespace Setting {
81 /**
82 * Type-level service identifier used by `Setting` global flags for the
83 * parsed value associated with a setting id.
84 *
85 * @stability unstable
86 * @category models
87 * @since 4.0.0
88 */
89 export type Identifier<Id extends string> = `effect/cli/GlobalFlag/${Id}`
90}
91
92/**
93 * Global flag discriminated union.
94 *
95 * @stability unstable
96 * @category models
97 * @since 4.0.0
98 */
99export type GlobalFlag<A> = Action<A> | Setting<any, A>
100
101/* ========================================================================== */
102/* Constructors */
103/* ========================================================================== */
104
105/**
106 * Creates an Action flag that performs a side effect and exits.
107 *
108 * @stability unstable
109 * @category constructors
110 * @since 4.0.0
111 */
112export const Action = <A>(options: {
113 readonly flag: Flag.Flag<A>
114 readonly run: (
115 value: A,
116 context: HandlerContext
117 ) => Effect.Effect<void>
118}): Action<A> => ({
119 _tag: "Action",
120 flag: options.flag,
121 run: options.run
122})
123
124/**
125 * Creates a Setting flag that configures the command handler's environment.
126 *
127 * @stability unstable
128 * @category constructors
129 * @since 4.0.0
130 */
131export const Setting = <const Id extends string>(
132 id: Id
133) =>
134<A>(options: {
135 readonly flag: Flag.Flag<A>
136}): Setting<Id, A> => {
137 settingIdCounter += 1
138 const ref = Context.Service<Setting.Identifier<Id>, A>(
139 `effect/cli/GlobalFlag/${id}/${settingIdCounter}`
140 )
141 return Object.assign(ref, {
142 _tag: "Setting" as const,
143 id,
144 flag: options.flag
145 })
146}
147
148let settingIdCounter = 0
149
150/* ========================================================================== */
151/* Built-in Flag References */
152/* ========================================================================== */
153
154/**
155 * Defines the `--help` / `-h` global flag, which shows help documentation for the
156 * active command path.
157 *
158 * @see {@link BuiltIns} for the default list containing this flag
159 * @see {@link Action} for defining custom action global flags
160 *
161 * @stability unstable
162 * @category references
163 * @since 4.0.0
164 */
165export const Help: Action<boolean> = Action({
166 flag: Flag.Boolean("help").pipe(
167 Flag.withAlias("h"),
168 Flag.withDescription("Show help information"),
169 Flag.withDefault(false)
170 ),
171 run: Effect.fnUntraced(function*(_, { builtIns, command, commandPath }) {
172 const formatter = yield* CliOutput.Formatter
173 const helpDoc = yield* HelpInternal.getHelpForCommandPath(command, commandPath, builtIns)
174 yield* Console.log(formatter.formatHelpDoc(helpDoc))
175 })
176})
177
178/**
179 * Defines the global action flag for showing command version information.
180 *
181 * **When to use**
182 *
183 * Use to add a built-in `--version / -v` flag to a command runner.
184 *
185 * @stability unstable
186 * @category references
187 * @since 4.0.0
188 */
189export const Version: Action<boolean> = Action({
190 flag: Flag.Boolean("version").pipe(
191 Flag.withAlias("v"),
192 Flag.withDescription("Show version information"),
193 Flag.withDefault(false)
194 ),
195 run: Effect.fnUntraced(function*(_, { command, version }) {
196 const formatter = yield* CliOutput.Formatter
197 yield* Console.log(formatter.formatVersion(command.name, version))
198 })
199})
200
201/**
202 * Defines the global action flag for starting interactive wizard mode.
203 *
204 * **Details**
205 *
206 * `Command.run` and `Command.runWith` handle this action specially so the
207 * generated arguments can be passed back through the command parser.
208 *
209 * @stability unstable
210 * @category references
211 * @since 4.0.0
212 */
213export const Wizard: Action<boolean> = Action({
214 flag: Flag.Boolean("wizard").pipe(
215 Flag.withDescription("Start wizard mode for a command"),
216 Flag.withDefault(false)
217 ),
218 run: () => Effect.void
219})
220
221/**
222 * Defines the `--completions` global flag, which prints a shell completion script for
223 * the given shell.
224 *
225 * **Details**
226 *
227 * Accepted values are `bash`, `zsh`, `fish`, and `sh`; `sh` is normalized to
228 * `bash`.
229 *
230 * @stability unstable
231 * @category references
232 * @since 4.0.0
233 */
234export const Completions: Action<Option.Option<"bash" | "zsh" | "fish">> = Action({
235 flag: Flag.Literals("completions", ["bash", "zsh", "fish", "sh"] as const)
236 .pipe(
237 Flag.optional,
238 Flag.map((v) => Option.map(v, (s) => s === "sh" ? "bash" : s)),
239 Flag.withMetavar("<bash|zsh|fish|sh>"),
240 Flag.withDescription("Print shell completion script")
241 ),
242 run: Effect.fnUntraced(function*(shell, { command }) {
243 if (Option.isNone(shell)) return
244 const descriptor = CommandDescriptor.fromCommand(command)
245 yield* Console.log(
246 Completions_.generate(command.name, shell.value, descriptor)
247 )
248 })
249})
250
251/**
252 * Defines the global setting flag for command log level.
253 *
254 * **When to use**
255 *
256 * Use to add a built-in `--log-level` option that configures the minimum log
257 * level for the command.
258 *
259 * @stability unstable
260 * @category references
261 * @since 4.0.0
262 */
263export const LogLevel: Setting<"log-level", Option.Option<LogLevelType>> = Setting("log-level")({
264 flag: Flag.ChoiceWithValue(
265 "log-level",
266 [
267 ["all", "All"],
268 ["trace", "Trace"],
269 ["debug", "Debug"],
270 ["info", "Info"],
271 ["warn", "Warn"],
272 ["warning", "Warn"],
273 ["error", "Error"],
274 ["fatal", "Fatal"],
275 ["none", "None"]
276 ] as const
277 ).pipe(
278 Flag.optional,
279 Flag.withDescription("Sets the minimum log level"),
280 Flag.withMetavar("<all|trace|debug|info|warn|warning|error|fatal|none>")
281 )
282})
283
284/* ========================================================================== */
285/* References */
286/* ========================================================================== */
287
288/**
289 * Built-in global flags in default precedence order.
290 *
291 * **When to use**
292 *
293 * Use when extending or inspecting the default global-flag set that
294 * `Command.runWith` prepends before user-defined global flags.
295 *
296 * **Details**
297 *
298 * The built-ins are `Help`, `Version`, `Wizard`, `Completions`, and `LogLevel`.
299 * `Command.runWith` prepends these built-ins when collecting and parsing global
300 * flags.
301 *
302 * **Gotchas**
303 *
304 * Action flags are processed in active flag order and the first present action
305 * exits, so this array controls built-in action precedence.
306 *
307 * @see {@link Help} for the help action flag
308 * @see {@link Version} for the version action flag
309 * @see {@link Completions} for the shell-completions action flag
310 * @see {@link LogLevel} for the built-in log-level setting flag
311 *
312 * @stability unstable
313 * @category references
314 * @since 4.0.0
315 */
316export const BuiltIns: readonly [
317 Action<boolean>,
318 Action<boolean>,
319 Action<boolean>,
320 Action<Option.Option<"bash" | "zsh" | "fish">>,
321 Setting<"log-level", Option.Option<LogLevelType>>
322] = [Help, Version, Wizard, Completions, LogLevel]
323
324/**
325 * Global flag included in the default command-runner configuration.
326 *
327 * @stability unstable
328 * @category models
329 * @since 4.0.0
330 */
331export type BuiltIn = typeof BuiltIns[number]