| 1 | /** |
| 2 | * Defines named options for command-line applications. |
| 3 | * |
| 4 | * A `Flag<A>` describes how to read one named value from parsed command-line |
| 5 | * input, validate it, and produce an `A`. Flags are useful for inputs such as |
| 6 | * ports, verbosity switches, configuration files, output directories, choices, |
| 7 | * secrets, and repeated values. The helpers here build flags with aliases, |
| 8 | * defaults, optional values, prompts, configuration fallbacks, validation, and |
| 9 | * value transformations. |
| 10 | * |
| 11 | * @stability unstable |
| 12 | * @since 4.0.0 |
| 13 | */ |
| 14 | import type * as Config from "../Config.ts" |
| 15 | import type * as Effect from "../Effect.ts" |
| 16 | import { dual, type LazyArg } from "../Function.ts" |
| 17 | import type * as Option from "../Option.ts" |
| 18 | import type * as Redacted_ from "../Redacted.ts" |
| 19 | import type * as Result from "../Result.ts" |
| 20 | import type * as Schema from "../Schema.ts" |
| 21 | import type * as CliError from "./CliError.ts" |
| 22 | import type { Environment } from "./Command.ts" |
| 23 | import * as Param from "./Param.ts" |
| 24 | import type * as Primitive from "./Primitive.ts" |
| 25 | |
| 26 | // ------------------------------------------------------------------------------------- |
| 27 | // models |
| 28 | // ------------------------------------------------------------------------------------- |
| 29 | |
| 30 | /** |
| 31 | * Represents a command-line flag. |
| 32 | * |
| 33 | * @stability unstable |
| 34 | * @category models |
| 35 | * @since 4.0.0 |
| 36 | */ |
| 37 | export interface Flag<A> extends Param.Param<typeof Param.flagKind, A> {} |
| 38 | |
| 39 | // ------------------------------------------------------------------------------------- |
| 40 | // constructors |
| 41 | // ------------------------------------------------------------------------------------- |
| 42 | |
| 43 | /** |
| 44 | * Creates a string flag that accepts text input. |
| 45 | * |
| 46 | * **Example** (Creating string flags) |
| 47 | * |
| 48 | * ```ts import.meta.vitest |
| 49 | * import { Flag } from "effect/cli" |
| 50 | * |
| 51 | * const nameFlag = Flag.String("name") |
| 52 | * // Usage: --name "John Doe" |
| 53 | * nameFlag.kind // => "flag" |
| 54 | * ``` |
| 55 | * |
| 56 | * @stability unstable |
| 57 | * @category constructors |
| 58 | * @since 4.0.0 |
| 59 | */ |
| 60 | export const String = (name: string): Flag<string> => Param.String(Param.flagKind, name) |
| 61 | |
| 62 | /** |
| 63 | * Creates a boolean flag that can be enabled or disabled. |
| 64 | * |
| 65 | * **Example** (Creating boolean flags) |
| 66 | * |
| 67 | * ```ts import.meta.vitest |
| 68 | * import { Flag } from "effect/cli" |
| 69 | * |
| 70 | * const verboseFlag = Flag.Boolean("verbose") |
| 71 | * // Usage: --verbose (true) or --no-verbose (false) |
| 72 | * // Omission fails unless the flag is made optional or given a fallback. |
| 73 | * verboseFlag.kind // => "flag" |
| 74 | * ``` |
| 75 | * |
| 76 | * @stability unstable |
| 77 | * @category constructors |
| 78 | * @since 4.0.0 |
| 79 | */ |
| 80 | export const Boolean = (name: string): Flag<boolean> => Param.Boolean(Param.flagKind, name) |
| 81 | |
| 82 | /** |
| 83 | * Creates an integer flag that accepts whole number input. |
| 84 | * |
| 85 | * **Example** (Creating integer flags) |
| 86 | * |
| 87 | * ```ts import.meta.vitest |
| 88 | * import { Flag } from "effect/cli" |
| 89 | * |
| 90 | * const portFlag = Flag.Int("port") |
| 91 | * // Usage: --port 8080 |
| 92 | * portFlag.kind // => "flag" |
| 93 | * ``` |
| 94 | * |
| 95 | * @stability unstable |
| 96 | * @category constructors |
| 97 | * @since 4.0.0 |
| 98 | */ |
| 99 | export const Int = (name: string): Flag<number> => Param.Int(Param.flagKind, name) |
| 100 | |
| 101 | /** |
| 102 | * Creates a float flag that accepts decimal number input. |
| 103 | * |
| 104 | * **Example** (Creating float flags) |
| 105 | * |
| 106 | * ```ts import.meta.vitest |
| 107 | * import { Flag } from "effect/cli" |
| 108 | * |
| 109 | * const rateFlag = Flag.Finite("rate") |
| 110 | * // Usage: --rate 3.14 |
| 111 | * rateFlag.kind // => "flag" |
| 112 | * ``` |
| 113 | * |
| 114 | * @stability unstable |
| 115 | * @category constructors |
| 116 | * @since 4.0.0 |
| 117 | */ |
| 118 | export const Finite = (name: string): Flag<number> => Param.Finite(Param.flagKind, name) |
| 119 | |
| 120 | /** |
| 121 | * Creates a date flag that accepts date input in ISO format. |
| 122 | * |
| 123 | * **Example** (Creating date flags) |
| 124 | * |
| 125 | * ```ts import.meta.vitest |
| 126 | * import { Flag } from "effect/cli" |
| 127 | * |
| 128 | * const startDateFlag = Flag.Date("start-date") |
| 129 | * // Usage: --start-date 2023-12-25 |
| 130 | * startDateFlag.kind // => "flag" |
| 131 | * ``` |
| 132 | * |
| 133 | * @stability unstable |
| 134 | * @category constructors |
| 135 | * @since 4.0.0 |
| 136 | */ |
| 137 | export const Date = (name: string): Flag<globalThis.Date> => Param.Date(Param.flagKind, name) |
| 138 | |
| 139 | /** |
| 140 | * Constructs option parameters that represent a choice between several inputs. |
| 141 | * Each tuple maps a string flag value to an associated typed value. |
| 142 | * |
| 143 | * **Example** (Creating flag choices with values) |
| 144 | * |
| 145 | * ```ts import.meta.vitest |
| 146 | * import { Flag } from "effect/cli" |
| 147 | * |
| 148 | * // simple enum like choice mapping directly to string union |
| 149 | * const color = Flag.Literals("color", ["red", "green", "blue"]) |
| 150 | * |
| 151 | * // choice with custom value mapping |
| 152 | * const logLevel = Flag.ChoiceWithValue("log-level", [ |
| 153 | * ["debug", "Debug" as const], |
| 154 | * ["info", "Info" as const], |
| 155 | * ["error", "Error" as const] |
| 156 | * ]) |
| 157 | * const kinds = [color.kind, logLevel.kind] // => ["flag", "flag"] |
| 158 | * ``` |
| 159 | * |
| 160 | * @stability unstable |
| 161 | * @category constructors |
| 162 | * @since 4.0.0 |
| 163 | */ |
| 164 | export const ChoiceWithValue = <const Choice extends ReadonlyArray<readonly [string, any]>>( |
| 165 | name: string, |
| 166 | choices: Choice |
| 167 | ): Flag<Choice[number][1]> => Param.ChoiceWithValue(Param.flagKind, name, choices) |
| 168 | |
| 169 | /** |
| 170 | * Accepts one of the provided strings. An empty array rejects all input. |
| 171 | * |
| 172 | * @see {@link ChoiceWithValue} for mapping accepted strings to different typed values |
| 173 | * |
| 174 | * @stability unstable |
| 175 | * @category constructors |
| 176 | * @since 4.0.0 |
| 177 | */ |
| 178 | export const Literals = <const Literals extends ReadonlyArray<string>>( |
| 179 | name: string, |
| 180 | literals: Literals |
| 181 | ): Flag<Literals[number]> => Param.Literals(Param.flagKind, name, literals) |
| 182 | |
| 183 | /** |
| 184 | * Creates a path flag that accepts file system path input with validation options. |
| 185 | * |
| 186 | * **Example** (Creating path flags) |
| 187 | * |
| 188 | * ```ts import.meta.vitest |
| 189 | * import { Flag } from "effect/cli" |
| 190 | * |
| 191 | * // Basic path flag |
| 192 | * const pathFlag = Flag.Path("config-path") |
| 193 | * |
| 194 | * // File-only path that must exist |
| 195 | * const fileFlag = Flag.Path("input-file", { |
| 196 | * pathType: "file", |
| 197 | * mustExist: true |
| 198 | * }) |
| 199 | * |
| 200 | * // Directory path with custom type name |
| 201 | * const dirFlag = Flag.Path("output-dir", { |
| 202 | * pathType: "directory", |
| 203 | * typeName: "OUTPUT_DIRECTORY" |
| 204 | * }) |
| 205 | * const kinds = [pathFlag.kind, fileFlag.kind, dirFlag.kind] // => ["flag", "flag", "flag"] |
| 206 | * ``` |
| 207 | * |
| 208 | * @stability unstable |
| 209 | * @category constructors |
| 210 | * @since 4.0.0 |
| 211 | */ |
| 212 | export const Path = (name: string, options?: { |
| 213 | readonly pathType?: "file" | "directory" | "either" | undefined |
| 214 | readonly mustExist?: boolean | undefined |
| 215 | readonly typeName?: string | undefined |
| 216 | }): Flag<string> => Param.Path(Param.flagKind, name, options) |
| 217 | |
| 218 | /** |
| 219 | * Creates a file path flag that accepts file paths with optional existence validation. |
| 220 | * |
| 221 | * **Example** (Creating file flags) |
| 222 | * |
| 223 | * ```ts import.meta.vitest |
| 224 | * import { Flag } from "effect/cli" |
| 225 | * |
| 226 | * // Basic file flag |
| 227 | * const inputFlag = Flag.File("input") |
| 228 | * // Usage: --input ./data.json |
| 229 | * |
| 230 | * // File that must exist |
| 231 | * const configFlag = Flag.File("config", { mustExist: true }) |
| 232 | * // Usage: --config ./config.yaml (file must exist) |
| 233 | * const kinds = [inputFlag.kind, configFlag.kind] // => ["flag", "flag"] |
| 234 | * ``` |
| 235 | * |
| 236 | * @stability unstable |
| 237 | * @category constructors |
| 238 | * @since 4.0.0 |
| 239 | */ |
| 240 | export const File = (name: string, options?: { |
| 241 | readonly mustExist?: boolean | undefined |
| 242 | }): Flag<string> => Param.File(Param.flagKind, name, options) |
| 243 | |
| 244 | /** |
| 245 | * Creates a directory path flag that accepts directory paths with optional existence validation. |
| 246 | * |
| 247 | * **Example** (Creating directory flags) |
| 248 | * |
| 249 | * ```ts import.meta.vitest |
| 250 | * import { Flag } from "effect/cli" |
| 251 | * |
| 252 | * // Basic directory flag |
| 253 | * const outputFlag = Flag.Directory("output") |
| 254 | * // Usage: --output ./build |
| 255 | * |
| 256 | * // Directory that must exist |
| 257 | * const sourceFlag = Flag.Directory("source", { mustExist: true }) |
| 258 | * // Usage: --source ./src (directory must exist) |
| 259 | * const kinds = [outputFlag.kind, sourceFlag.kind] // => ["flag", "flag"] |
| 260 | * ``` |
| 261 | * |
| 262 | * @stability unstable |
| 263 | * @category constructors |
| 264 | * @since 4.0.0 |
| 265 | */ |
| 266 | export const Directory = (name: string, options?: { |
| 267 | readonly mustExist?: boolean | undefined |
| 268 | }): Flag<string> => Param.Directory(Param.flagKind, name, options) |
| 269 | |
| 270 | /** |
| 271 | * Creates a string flag whose parsed value is wrapped in `Redacted.Redacted` so |
| 272 | * stringification and logging redact the value. |
| 273 | * |
| 274 | * **Gotchas** |
| 275 | * |
| 276 | * Values supplied on the command line may still be visible to the operating |
| 277 | * system or shell history. |
| 278 | * |
| 279 | * **Example** (Creating redacted flags) |
| 280 | * |
| 281 | * ```ts import.meta.vitest |
| 282 | * import { Effect, FileSystem, Layer, Path, Redacted, Stdio, Terminal } from "effect" |
| 283 | * import { Flag } from "effect/cli" |
| 284 | * import { ChildProcessSpawner } from "effect/process" |
| 285 | * |
| 286 | * const CliTestLayer = Layer.mergeAll( |
| 287 | * FileSystem.layerNoop({}), |
| 288 | * Path.layer, |
| 289 | * Stdio.layerTest({}), |
| 290 | * Layer.succeed(Terminal.Terminal, Terminal.make({ |
| 291 | * columns: Effect.succeed(80), |
| 292 | * rows: Effect.succeed(24), |
| 293 | * readInput: Effect.die("unused"), |
| 294 | * readLine: Effect.die("unused"), |
| 295 | * display: () => Effect.void |
| 296 | * })), |
| 297 | * Layer.succeed( |
| 298 | * ChildProcessSpawner.ChildProcessSpawner, |
| 299 | * ChildProcessSpawner.make(() => Effect.die("unused")) |
| 300 | * ) |
| 301 | * ) |
| 302 | * |
| 303 | * const passwordFlag = Flag.Redacted("password") |
| 304 | * |
| 305 | * const program = Effect.gen(function*() { |
| 306 | * const [, password] = yield* passwordFlag.parse({ |
| 307 | * arguments: [], |
| 308 | * flags: { "password": ["abc123"] } |
| 309 | * }) |
| 310 | * return Redacted.value(password).length |
| 311 | * }) |
| 312 | * |
| 313 | * await Effect.runPromise(program.pipe(Effect.provide(CliTestLayer))) // => 6 |
| 314 | * ``` |
| 315 | * |
| 316 | * @stability unstable |
| 317 | * @category constructors |
| 318 | * @since 4.0.0 |
| 319 | */ |
| 320 | export const Redacted = (name: string): Flag<Redacted_.Redacted<string>> => Param.Redacted(Param.flagKind, name) |
| 321 | |
| 322 | /** |
| 323 | * Creates a flag that reads and returns file content as a string. |
| 324 | * |
| 325 | * **Example** (Reading file text) |
| 326 | * |
| 327 | * ```ts import.meta.vitest |
| 328 | * import { Flag } from "effect/cli" |
| 329 | * |
| 330 | * const config = Flag.FileText("config-file") |
| 331 | * // --config-file ./app.json will read the file content |
| 332 | * config.kind // => "flag" |
| 333 | * ``` |
| 334 | * |
| 335 | * @stability unstable |
| 336 | * @category constructors |
| 337 | * @since 4.0.0 |
| 338 | */ |
| 339 | export const FileText = (name: string): Flag<string> => Param.FileText(Param.flagKind, name) |
| 340 | |
| 341 | /** |
| 342 | * Creates a flag that reads and parses the content of the specified file. |
| 343 | * |
| 344 | * **Details** |
| 345 | * |
| 346 | * The parser that is utilized will depend on the specified `format`, or the |
| 347 | * extension of the file passed on the command-line if no `format` is specified. |
| 348 | * |
| 349 | * **Example** (Parsing file contents) |
| 350 | * |
| 351 | * ```ts import.meta.vitest |
| 352 | * import { Flag } from "effect/cli" |
| 353 | * |
| 354 | * // Will use the extension of the file passed on the command line to determine |
| 355 | * // the parser to use |
| 356 | * const config = Flag.FileParse("config") |
| 357 | * |
| 358 | * // Will use the JSON parser |
| 359 | * const jsonConfig = Flag.FileParse("json-config", { format: "json" }) |
| 360 | * const kinds = [config.kind, jsonConfig.kind] // => ["flag", "flag"] |
| 361 | * ``` |
| 362 | * |
| 363 | * @stability unstable |
| 364 | * @category constructors |
| 365 | * @since 4.0.0 |
| 366 | */ |
| 367 | export const FileParse = ( |
| 368 | name: string, |
| 369 | options?: Primitive.FileParseOptions | undefined |
| 370 | ): Flag<unknown> => Param.FileParse(Param.flagKind, name, options) |
| 371 | |
| 372 | /** |
| 373 | * Creates a flag that reads and validates file content using the specified |
| 374 | * schema. |
| 375 | * |
| 376 | * **Example** (Validating file contents) |
| 377 | * |
| 378 | * ```ts import.meta.vitest |
| 379 | * import { Schema } from "effect" |
| 380 | * import { Flag } from "effect/cli" |
| 381 | * |
| 382 | * const ConfigSchema = Schema.Struct({ |
| 383 | * port: Schema.Number, |
| 384 | * host: Schema.String |
| 385 | * }) |
| 386 | * |
| 387 | * const config = Flag.FileSchema("config", ConfigSchema, { format: "json" }) |
| 388 | * config.kind // => "flag" |
| 389 | * ``` |
| 390 | * |
| 391 | * @stability unstable |
| 392 | * @category constructors |
| 393 | * @since 4.0.0 |
| 394 | */ |
| 395 | export const FileSchema = <A>( |
| 396 | name: string, |
| 397 | schema: Schema.ConstraintDecoder<A, Environment>, |
| 398 | options?: Primitive.FileSchemaOptions | undefined |
| 399 | ): Flag<A> => Param.FileSchema(Param.flagKind, name, schema, options) |
| 400 | |
| 401 | /** |
| 402 | * Creates a flag that parses key=value pairs. |
| 403 | * |
| 404 | * **When to use** |
| 405 | * |
| 406 | * Use when you need a CLI flag that accepts one or more `key=value` |
| 407 | * configuration entries. |
| 408 | * |
| 409 | * **Details** |
| 410 | * |
| 411 | * Requires at least one key=value pair. Multiple pairs are merged into a single |
| 412 | * record. |
| 413 | * |
| 414 | * **Example** (Parsing key-value pairs) |
| 415 | * |
| 416 | * ```ts import.meta.vitest |
| 417 | * import { Flag } from "effect/cli" |
| 418 | * |
| 419 | * const env = Flag.KeyValuePair("env") |
| 420 | * // --env FOO=bar --env BAZ=qux will parse to { FOO: "bar", BAZ: "qux" } |
| 421 | * env.kind // => "flag" |
| 422 | * ``` |
| 423 | * |
| 424 | * @stability unstable |
| 425 | * @category constructors |
| 426 | * @since 4.0.0 |
| 427 | */ |
| 428 | export const KeyValuePair = (name: string): Flag<Record<string, string>> => Param.KeyValuePair(Param.flagKind, name) |
| 429 | |
| 430 | /** |
| 431 | * A flag that always fails to parse. |
| 432 | * |
| 433 | * **Example** (Creating sentinel flags) |
| 434 | * |
| 435 | * ```ts import.meta.vitest |
| 436 | * import { Flag } from "effect/cli" |
| 437 | * |
| 438 | * const makeValueFlag = (includeValue: boolean) => |
| 439 | * includeValue ? Flag.String("value") : Flag.Never |
| 440 | * |
| 441 | * makeValueFlag(true) === Flag.Never // => false |
| 442 | * makeValueFlag(false) === Flag.Never // => true |
| 443 | * ``` |
| 444 | * |
| 445 | * @stability unstable |
| 446 | * @category constructors |
| 447 | * @since 4.0.0 |
| 448 | */ |
| 449 | export const Never: Flag<never> = Param.Never(Param.flagKind) |
| 450 | |
| 451 | // ------------------------------------------------------------------------------------- |
| 452 | // combinators |
| 453 | // ------------------------------------------------------------------------------------- |
| 454 | |
| 455 | /** |
| 456 | * Adds an alias to a flag, allowing it to be referenced by multiple names. |
| 457 | * |
| 458 | * **Example** (Adding flag aliases) |
| 459 | * |
| 460 | * ```ts import.meta.vitest |
| 461 | * import { Flag } from "effect/cli" |
| 462 | * |
| 463 | * // Flag can be used as both --verbose and -v |
| 464 | * const verboseFlag = Flag.Boolean("verbose").pipe( |
| 465 | * Flag.withAlias("v") |
| 466 | * ) |
| 467 | * |
| 468 | * // Multiple aliases can be chained |
| 469 | * const helpFlag = Flag.Boolean("help").pipe( |
| 470 | * Flag.withAlias("h"), |
| 471 | * Flag.withAlias("?") |
| 472 | * ) |
| 473 | * const kinds = [verboseFlag.kind, helpFlag.kind] // => ["flag", "flag"] |
| 474 | * ``` |
| 475 | * |
| 476 | * @stability unstable |
| 477 | * @category aliasing |
| 478 | * @since 4.0.0 |
| 479 | */ |
| 480 | export const withAlias: { |
| 481 | <A>(alias: string): (self: Flag<A>) => Flag<A> |
| 482 | <A>(self: Flag<A>, alias: string): Flag<A> |
| 483 | } = dual(2, <A>(self: Flag<A>, alias: string): Flag<A> => Param.withAlias(self, alias)) |
| 484 | |
| 485 | /** |
| 486 | * Adds a description to a flag for help documentation. |
| 487 | * |
| 488 | * **Example** (Adding help descriptions) |
| 489 | * |
| 490 | * ```ts import.meta.vitest |
| 491 | * import { Flag } from "effect/cli" |
| 492 | * |
| 493 | * const portFlag = Flag.Int("port").pipe( |
| 494 | * Flag.withDescription("The port number to listen on") |
| 495 | * ) |
| 496 | * |
| 497 | * const configFlag = Flag.File("config").pipe( |
| 498 | * Flag.withDescription("Path to the configuration file") |
| 499 | * ) |
| 500 | * const kinds = [portFlag.kind, configFlag.kind] // => ["flag", "flag"] |
| 501 | * ``` |
| 502 | * |
| 503 | * @stability unstable |
| 504 | * @category metadata |
| 505 | * @since 4.0.0 |
| 506 | */ |
| 507 | export const withDescription: { |
| 508 | <A>(description: string): (self: Flag<A>) => Flag<A> |
| 509 | <A>(self: Flag<A>, description: string): Flag<A> |
| 510 | } = dual(2, <A>(self: Flag<A>, description: string) => Param.withDescription(self, description)) |
| 511 | |
| 512 | // ------------------------------------------------------------------------------------- |
| 513 | // metadata |
| 514 | // ------------------------------------------------------------------------------------- |
| 515 | |
| 516 | /** |
| 517 | * Sets a custom metavar (placeholder name) for the flag in help documentation. |
| 518 | * |
| 519 | * **Details** |
| 520 | * |
| 521 | * The metavar is displayed in usage text to indicate what value the user should |
| 522 | * provide. For example, `--output FILE` shows `FILE` as the metavar. |
| 523 | * |
| 524 | * **Example** (Setting metavars) |
| 525 | * |
| 526 | * ```ts import.meta.vitest |
| 527 | * import { Flag } from "effect/cli" |
| 528 | * |
| 529 | * const databaseFlag = Flag.String("database-url").pipe( |
| 530 | * Flag.withMetavar("URL"), |
| 531 | * Flag.withDescription("Database connection URL") |
| 532 | * ) |
| 533 | * // In help: --database-url URL |
| 534 | * |
| 535 | * const timeoutFlag = Flag.Int("timeout").pipe( |
| 536 | * Flag.withMetavar("SECONDS") |
| 537 | * ) |
| 538 | * // In help: --timeout SECONDS |
| 539 | * const kinds = [databaseFlag.kind, timeoutFlag.kind] // => ["flag", "flag"] |
| 540 | * ``` |
| 541 | * |
| 542 | * @stability unstable |
| 543 | * @category metadata |
| 544 | * @since 4.0.0 |
| 545 | */ |
| 546 | export const withMetavar: { |
| 547 | <A>(metavar: string): (self: Flag<A>) => Flag<A> |
| 548 | <A>(self: Flag<A>, metavar: string): Flag<A> |
| 549 | } = dual(2, <A>(self: Flag<A>, metavar: string) => Param.withMetavar(self, metavar)) |
| 550 | |
| 551 | /** |
| 552 | * Hides a flag from generated help output and shell completions while keeping |
| 553 | * it fully parseable on the command line. |
| 554 | * |
| 555 | * **When to use** |
| 556 | * |
| 557 | * Use when experimental or internal flags should be accepted but not advertised, such as |
| 558 | * `--experimental-foo`, debug toggles, or escape hatches that are not yet committed to the |
| 559 | * public CLI surface. |
| 560 | * |
| 561 | * **Example** (Hiding a flag from help) |
| 562 | * |
| 563 | * ```ts import.meta.vitest |
| 564 | * import { Flag } from "effect/cli" |
| 565 | * |
| 566 | * // Flag still parses --experimental-foo, but it does not appear in --help. |
| 567 | * const experimental = Flag.Boolean("experimental-foo").pipe( |
| 568 | * Flag.withHidden |
| 569 | * ) |
| 570 | * experimental.kind // => "flag" |
| 571 | * ``` |
| 572 | * |
| 573 | * @stability unstable |
| 574 | * @category metadata |
| 575 | * @since 4.0.0 |
| 576 | */ |
| 577 | export const withHidden = <A>(self: Flag<A>): Flag<A> => Param.withHidden(self) |
| 578 | |
| 579 | /** |
| 580 | * Makes a flag optional, returning an Option type that can be None if not provided. |
| 581 | * |
| 582 | * **Example** (Making flags optional) |
| 583 | * |
| 584 | * ```ts import.meta.vitest |
| 585 | * import { Effect, FileSystem, Layer, Option, Path, Stdio, Terminal } from "effect" |
| 586 | * import { Flag } from "effect/cli" |
| 587 | * import { ChildProcessSpawner } from "effect/process" |
| 588 | * |
| 589 | * const CliTestLayer = Layer.mergeAll( |
| 590 | * FileSystem.layerNoop({}), |
| 591 | * Path.layer, |
| 592 | * Stdio.layerTest({}), |
| 593 | * Layer.succeed(Terminal.Terminal, Terminal.make({ |
| 594 | * columns: Effect.succeed(80), |
| 595 | * rows: Effect.succeed(24), |
| 596 | * readInput: Effect.die("unused"), |
| 597 | * readLine: Effect.die("unused"), |
| 598 | * display: () => Effect.void |
| 599 | * })), |
| 600 | * Layer.succeed( |
| 601 | * ChildProcessSpawner.ChildProcessSpawner, |
| 602 | * ChildProcessSpawner.make(() => Effect.die("unused")) |
| 603 | * ) |
| 604 | * ) |
| 605 | * |
| 606 | * const optionalPort = Flag.optional(Flag.Int("port")) |
| 607 | * |
| 608 | * const program = Effect.gen(function*() { |
| 609 | * const [, port] = yield* optionalPort.parse({ |
| 610 | * arguments: [], |
| 611 | * flags: { "port": ["4000"] } |
| 612 | * }) |
| 613 | * return port |
| 614 | * }) |
| 615 | * |
| 616 | * await Effect.runPromise(program.pipe(Effect.provide(CliTestLayer))) // => Option.some(4000) |
| 617 | * ``` |
| 618 | * |
| 619 | * @stability unstable |
| 620 | * @category optionality |
| 621 | * @since 4.0.0 |
| 622 | */ |
| 623 | export const optional = <A>(param: Flag<A>): Flag<Option.Option<A>> => Param.optional(param) |
| 624 | |
| 625 | /** |
| 626 | * Provides a default value for a flag when it's not specified. |
| 627 | * |
| 628 | * **Example** (Providing default values) |
| 629 | * |
| 630 | * ```ts import.meta.vitest |
| 631 | * import { Flag } from "effect/cli" |
| 632 | * |
| 633 | * const portFlag = Flag.Int("port").pipe( |
| 634 | * Flag.withDefault(8080) |
| 635 | * ) |
| 636 | * // If --port is not provided, defaults to 8080 |
| 637 | * |
| 638 | * const hostFlag = Flag.String("host").pipe( |
| 639 | * Flag.withDefault("localhost") |
| 640 | * ) |
| 641 | * // If --host is not provided, defaults to "localhost" |
| 642 | * const kinds = [portFlag.kind, hostFlag.kind] // => ["flag", "flag"] |
| 643 | * ``` |
| 644 | * |
| 645 | * @stability unstable |
| 646 | * @category optionality |
| 647 | * @since 4.0.0 |
| 648 | */ |
| 649 | export const withDefault: { |
| 650 | <const B>(defaultValue: B | Effect.Effect<B, CliError.CliError, Environment>): <A>(self: Flag<A>) => Flag<A | B> |
| 651 | <A, const B>(self: Flag<A>, defaultValue: B | Effect.Effect<B, CliError.CliError, Environment>): Flag<A | B> |
| 652 | } = Param.withDefault |
| 653 | |
| 654 | /** |
| 655 | * Adds a fallback config that is loaded when a required flag is missing. |
| 656 | * |
| 657 | * **Example** (Falling back to config) |
| 658 | * |
| 659 | * ```ts import.meta.vitest |
| 660 | * import { Config } from "effect" |
| 661 | * import { Flag } from "effect/cli" |
| 662 | * |
| 663 | * const verbose = Flag.Boolean("verbose").pipe( |
| 664 | * Flag.withFallbackConfig(Config.Boolean("VERBOSE")) |
| 665 | * ) |
| 666 | * verbose.kind // => "flag" |
| 667 | * ``` |
| 668 | * |
| 669 | * @stability unstable |
| 670 | * @category combinators |
| 671 | * @since 4.0.0 |
| 672 | */ |
| 673 | export const withFallbackConfig: { |
| 674 | <B>(config: Config.Config<B>): <A>(self: Flag<A>) => Flag<A | B> |
| 675 | <A, B>(self: Flag<A>, config: Config.Config<B>): Flag<A | B> |
| 676 | } = dual(2, <A, B>(self: Flag<A>, config: Config.Config<B>) => Param.withFallbackConfig(self, config)) |
| 677 | |
| 678 | /** |
| 679 | * Adds a fallback prompt that is shown when a required flag is missing. |
| 680 | * |
| 681 | * **Example** (Falling back to prompts) |
| 682 | * |
| 683 | * ```ts import.meta.vitest |
| 684 | * import { Flag, Prompt } from "effect/cli" |
| 685 | * |
| 686 | * const name = Flag.String("name").pipe( |
| 687 | * Flag.withFallbackPrompt(Prompt.String({ message: "Name" })) |
| 688 | * ) |
| 689 | * name.kind // => "flag" |
| 690 | * ``` |
| 691 | * |
| 692 | * @stability unstable |
| 693 | * @category combinators |
| 694 | * @since 4.0.0 |
| 695 | */ |
| 696 | export const withFallbackPrompt: { |
| 697 | <B>(prompt: Param.FallbackPrompt<B>): <A>(self: Flag<A>) => Flag<A | B> |
| 698 | <A, B>(self: Flag<A>, prompt: Param.FallbackPrompt<B>): Flag<A | B> |
| 699 | } = dual(2, <A, B>(self: Flag<A>, prompt: Param.FallbackPrompt<B>) => Param.withFallbackPrompt(self, prompt)) |
| 700 | |
| 701 | /** |
| 702 | * Transforms the parsed value of a flag using a mapping function. |
| 703 | * |
| 704 | * **Example** (Mapping parsed values) |
| 705 | * |
| 706 | * ```ts import.meta.vitest |
| 707 | * import { Flag } from "effect/cli" |
| 708 | * |
| 709 | * // Convert string to uppercase |
| 710 | * const nameFlag = Flag.String("name").pipe( |
| 711 | * Flag.map((name) => name.toUpperCase()) |
| 712 | * ) |
| 713 | * |
| 714 | * // Convert port to URL |
| 715 | * const urlFlag = Flag.Int("port").pipe( |
| 716 | * Flag.map((port) => `http://localhost:${port}`) |
| 717 | * ) |
| 718 | * const kinds = [nameFlag.kind, urlFlag.kind] // => ["flag", "flag"] |
| 719 | * ``` |
| 720 | * |
| 721 | * @stability unstable |
| 722 | * @category mapping |
| 723 | * @since 4.0.0 |
| 724 | */ |
| 725 | export const map: { |
| 726 | <A, B>(f: (a: A) => B): (self: Flag<A>) => Flag<B> |
| 727 | <A, B>(self: Flag<A>, f: (a: A) => B): Flag<B> |
| 728 | } = dual(2, <A, B>(self: Flag<A>, f: (a: A) => B) => Param.map(self, f)) |
| 729 | |
| 730 | /** |
| 731 | * Transforms the parsed value using an Effect that can perform IO operations. |
| 732 | * |
| 733 | * **Example** (Mapping parsed values effectfully) |
| 734 | * |
| 735 | * ```ts import.meta.vitest |
| 736 | * import { Effect, FileSystem, Layer, Path, Stdio, Terminal } from "effect" |
| 737 | * import { Flag } from "effect/cli" |
| 738 | * import { ChildProcessSpawner } from "effect/process" |
| 739 | * |
| 740 | * const CliTestLayer = Layer.mergeAll( |
| 741 | * FileSystem.layerNoop({}), |
| 742 | * Path.layer, |
| 743 | * Stdio.layerTest({}), |
| 744 | * Layer.succeed(Terminal.Terminal, Terminal.make({ |
| 745 | * columns: Effect.succeed(80), |
| 746 | * rows: Effect.succeed(24), |
| 747 | * readInput: Effect.die("unused"), |
| 748 | * readLine: Effect.die("unused"), |
| 749 | * display: () => Effect.void |
| 750 | * })), |
| 751 | * Layer.succeed( |
| 752 | * ChildProcessSpawner.ChildProcessSpawner, |
| 753 | * ChildProcessSpawner.make(() => Effect.die("unused")) |
| 754 | * ) |
| 755 | * ) |
| 756 | * |
| 757 | * const upperName = Flag.String("name").pipe( |
| 758 | * Flag.mapEffect((name) => Effect.succeed(name.toUpperCase())) |
| 759 | * ) |
| 760 | * |
| 761 | * const [, value] = await Effect.runPromise( |
| 762 | * upperName.parse({ |
| 763 | * arguments: [], |
| 764 | * flags: { name: ["alice"] } |
| 765 | * }).pipe(Effect.provide(CliTestLayer)) |
| 766 | * ) |
| 767 | * value // => "ALICE" |
| 768 | * ``` |
| 769 | * |
| 770 | * @stability unstable |
| 771 | * @category mapping |
| 772 | * @since 4.0.0 |
| 773 | */ |
| 774 | export const mapEffect: { |
| 775 | <A, B>( |
| 776 | f: (a: A) => Effect.Effect<B, CliError.CliError, Environment> |
| 777 | ): (self: Flag<A>) => Flag<B> |
| 778 | <A, B>( |
| 779 | self: Flag<A>, |
| 780 | f: (a: A) => Effect.Effect<B, CliError.CliError, Environment> |
| 781 | ): Flag<B> |
| 782 | } = dual(2, <A, B>( |
| 783 | self: Flag<A>, |
| 784 | f: (a: A) => Effect.Effect<B, CliError.CliError, Environment> |
| 785 | ) => Param.mapEffect(self, f)) |
| 786 | |
| 787 | /** |
| 788 | * Transforms the parsed value using a function that might throw, with error handling. |
| 789 | * |
| 790 | * **Example** (Mapping thrown errors) |
| 791 | * |
| 792 | * ```ts import.meta.vitest |
| 793 | * import { Effect, FileSystem, Layer, Path, Stdio, Terminal } from "effect" |
| 794 | * import { Flag } from "effect/cli" |
| 795 | * import { ChildProcessSpawner } from "effect/process" |
| 796 | * |
| 797 | * const CliTestLayer = Layer.mergeAll( |
| 798 | * FileSystem.layerNoop({}), |
| 799 | * Path.layer, |
| 800 | * Stdio.layerTest({}), |
| 801 | * Layer.succeed(Terminal.Terminal, Terminal.make({ |
| 802 | * columns: Effect.succeed(80), |
| 803 | * rows: Effect.succeed(24), |
| 804 | * readInput: Effect.die("unused"), |
| 805 | * readLine: Effect.die("unused"), |
| 806 | * display: () => Effect.void |
| 807 | * })), |
| 808 | * Layer.succeed( |
| 809 | * ChildProcessSpawner.ChildProcessSpawner, |
| 810 | * ChildProcessSpawner.make(() => Effect.die("unused")) |
| 811 | * ) |
| 812 | * ) |
| 813 | * |
| 814 | * // Parse JSON string with error handling |
| 815 | * const jsonFlag = Flag.String("config").pipe( |
| 816 | * Flag.mapTryCatch( |
| 817 | * (json) => JSON.parse(json), |
| 818 | * (error) => `Invalid JSON: ${error}` |
| 819 | * ) |
| 820 | * ) |
| 821 | * |
| 822 | * // Parse URL with error handling |
| 823 | * const urlFlag = Flag.String("url").pipe( |
| 824 | * Flag.mapTryCatch( |
| 825 | * (url) => new URL(url), |
| 826 | * (error) => `Invalid URL: ${error}` |
| 827 | * ) |
| 828 | * ) |
| 829 | * |
| 830 | * const [, value] = await Effect.runPromise( |
| 831 | * jsonFlag.parse({ |
| 832 | * arguments: [], |
| 833 | * flags: { config: ['{"enabled":true}'] } |
| 834 | * }).pipe(Effect.provide(CliTestLayer)) |
| 835 | * ) |
| 836 | * value // => { enabled: true } |
| 837 | * ``` |
| 838 | * |
| 839 | * @stability unstable |
| 840 | * @category mapping |
| 841 | * @since 4.0.0 |
| 842 | */ |
| 843 | export const mapTryCatch: { |
| 844 | <A, B>(f: (a: A) => B, onError: (error: unknown) => string): (self: Flag<A>) => Flag<B> |
| 845 | <A, B>(self: Flag<A>, f: (a: A) => B, onError: (error: unknown) => string): Flag<B> |
| 846 | } = dual(3, <A, B>( |
| 847 | self: Flag<A>, |
| 848 | f: (a: A) => B, |
| 849 | onError: (error: unknown) => string |
| 850 | ) => Param.mapTryCatch(self, f, onError)) |
| 851 | |
| 852 | /** |
| 853 | * Ensures a flag is specified at least a minimum number of times. |
| 854 | * |
| 855 | * **Example** (Requiring repeated values) |
| 856 | * |
| 857 | * ```ts import.meta.vitest |
| 858 | * import { Flag } from "effect/cli" |
| 859 | * |
| 860 | * const sourceFlag = Flag.atLeast(Flag.File("source"), 2) |
| 861 | * // Requires at least 2 source files |
| 862 | * // Usage: --source file1.ts --source file2.ts |
| 863 | * |
| 864 | * const tagFlag = Flag.String("tag").pipe( |
| 865 | * Flag.atLeast(1) |
| 866 | * ) |
| 867 | * // Requires at least 1 tag |
| 868 | * const kinds = [sourceFlag.kind, tagFlag.kind] // => ["flag", "flag"] |
| 869 | * ``` |
| 870 | * |
| 871 | * @stability unstable |
| 872 | * @category repetition |
| 873 | * @since 4.0.0 |
| 874 | */ |
| 875 | export const atLeast: { |
| 876 | <A>(min: number): (self: Flag<A>) => Flag<ReadonlyArray<A>> |
| 877 | <A>(self: Flag<A>, min: number): Flag<ReadonlyArray<A>> |
| 878 | } = dual(2, <A>(self: Flag<A>, min: number) => Param.atLeast(self, min)) |
| 879 | |
| 880 | /** |
| 881 | * Ensures a flag is specified at most a maximum number of times. |
| 882 | * |
| 883 | * **Example** (Limiting repeated values) |
| 884 | * |
| 885 | * ```ts import.meta.vitest |
| 886 | * import { Flag } from "effect/cli" |
| 887 | * |
| 888 | * const warningFlag = Flag.atMost(Flag.String("warning"), 3) |
| 889 | * // Allows up to 3 warning flags |
| 890 | * // Usage: --warning w1 --warning w2 --warning w3 |
| 891 | * |
| 892 | * const debugFlag = Flag.String("debug").pipe( |
| 893 | * Flag.atMost(1) |
| 894 | * ) |
| 895 | * // Allows at most 1 debug flag |
| 896 | * const kinds = [warningFlag.kind, debugFlag.kind] // => ["flag", "flag"] |
| 897 | * ``` |
| 898 | * |
| 899 | * @stability unstable |
| 900 | * @category repetition |
| 901 | * @since 4.0.0 |
| 902 | */ |
| 903 | export const atMost: { |
| 904 | <A>(max: number): (self: Flag<A>) => Flag<ReadonlyArray<A>> |
| 905 | <A>(self: Flag<A>, max: number): Flag<ReadonlyArray<A>> |
| 906 | } = dual(2, <A>(self: Flag<A>, max: number) => Param.atMost(self, max)) |
| 907 | |
| 908 | /** |
| 909 | * Ensures a flag is specified between a minimum and maximum number of times. |
| 910 | * |
| 911 | * **Example** (Bounding repeated values) |
| 912 | * |
| 913 | * ```ts import.meta.vitest |
| 914 | * import { Flag } from "effect/cli" |
| 915 | * |
| 916 | * const hostFlag = Flag.between(Flag.String("host"), 1, 3) |
| 917 | * // Requires 1-3 host flags |
| 918 | * // Usage: --host host1 --host host2 |
| 919 | * |
| 920 | * const excludeFlag = Flag.String("exclude").pipe( |
| 921 | * Flag.between(0, 5) |
| 922 | * ) |
| 923 | * // Allows 0-5 exclude patterns |
| 924 | * const kinds = [hostFlag.kind, excludeFlag.kind] // => ["flag", "flag"] |
| 925 | * ``` |
| 926 | * |
| 927 | * @stability unstable |
| 928 | * @category repetition |
| 929 | * @since 4.0.0 |
| 930 | */ |
| 931 | export const between: { |
| 932 | <A>(min: number, max: number): (self: Flag<A>) => Flag<ReadonlyArray<A>> |
| 933 | <A>(self: Flag<A>, min: number, max: number): Flag<ReadonlyArray<A>> |
| 934 | } = dual(3, <A>(self: Flag<A>, min: number, max: number) => Param.between(self, min, max)) |
| 935 | |
| 936 | /** |
| 937 | * Transforms and filters a flag value, failing with a custom error if the transformation returns None. |
| 938 | * |
| 939 | * **Example** (Filtering and transforming values) |
| 940 | * |
| 941 | * ```ts import.meta.vitest |
| 942 | * import { Option } from "effect" |
| 943 | * import { Flag } from "effect/cli" |
| 944 | * |
| 945 | * // Parse positive integers only |
| 946 | * const positiveInt = Flag.Int("count").pipe( |
| 947 | * Flag.filterMap( |
| 948 | * (n) => n > 0 ? Option.some(n) : Option.none(), |
| 949 | * (n) => `Expected positive integer, got ${n}` |
| 950 | * ) |
| 951 | * ) |
| 952 | * |
| 953 | * // Parse valid email addresses |
| 954 | * const emailFlag = Flag.String("email").pipe( |
| 955 | * Flag.filterMap( |
| 956 | * (email) => email.includes("@") ? Option.some(email) : Option.none(), |
| 957 | * (email) => `Invalid email address: ${email}` |
| 958 | * ) |
| 959 | * ) |
| 960 | * const kinds = [positiveInt.kind, emailFlag.kind] // => ["flag", "flag"] |
| 961 | * ``` |
| 962 | * |
| 963 | * @stability unstable |
| 964 | * @category filtering |
| 965 | * @since 4.0.0 |
| 966 | */ |
| 967 | export const filterMap: { |
| 968 | <A, B>(f: (a: A) => Option.Option<B>, onNone: (a: A) => string): (self: Flag<A>) => Flag<B> |
| 969 | <A, B>(self: Flag<A>, f: (a: A) => Option.Option<B>, onNone: (a: A) => string): Flag<B> |
| 970 | } = dual(3, <A, B>( |
| 971 | self: Flag<A>, |
| 972 | f: (a: A) => Option.Option<B>, |
| 973 | onNone: (a: A) => string |
| 974 | ) => Param.filterMap(self, f, onNone)) |
| 975 | |
| 976 | /** |
| 977 | * Filters a flag value based on a predicate, failing with a custom error if the predicate returns false. |
| 978 | * |
| 979 | * **Example** (Filtering parsed values) |
| 980 | * |
| 981 | * ```ts import.meta.vitest |
| 982 | * import { Flag } from "effect/cli" |
| 983 | * |
| 984 | * // Ensure port is in valid range |
| 985 | * const portFlag = Flag.Int("port").pipe( |
| 986 | * Flag.filter( |
| 987 | * (port) => port >= 1 && port <= 65535, |
| 988 | * (port) => `Port ${port} is out of range (1-65535)` |
| 989 | * ) |
| 990 | * ) |
| 991 | * |
| 992 | * // Ensure non-empty string |
| 993 | * const nameFlag = Flag.String("name").pipe( |
| 994 | * Flag.filter( |
| 995 | * (name) => name.trim().length > 0, |
| 996 | * () => "Name cannot be empty" |
| 997 | * ) |
| 998 | * ) |
| 999 | * const kinds = [portFlag.kind, nameFlag.kind] // => ["flag", "flag"] |
| 1000 | * ``` |
| 1001 | * |
| 1002 | * @stability unstable |
| 1003 | * @category filtering |
| 1004 | * @since 4.0.0 |
| 1005 | */ |
| 1006 | export const filter: { |
| 1007 | <A>(predicate: (a: A) => boolean, onFalse: (a: A) => string): (self: Flag<A>) => Flag<A> |
| 1008 | <A>(self: Flag<A>, predicate: (a: A) => boolean, onFalse: (a: A) => string): Flag<A> |
| 1009 | } = dual(3, <A>( |
| 1010 | self: Flag<A>, |
| 1011 | predicate: (a: A) => boolean, |
| 1012 | onFalse: (a: A) => string |
| 1013 | ) => Param.filter(self, predicate, onFalse)) |
| 1014 | |
| 1015 | /** |
| 1016 | * Provides an alternative flag if the first one fails to parse. |
| 1017 | * |
| 1018 | * **Example** (Falling back to another flag) |
| 1019 | * |
| 1020 | * ```ts import.meta.vitest |
| 1021 | * import { Flag } from "effect/cli" |
| 1022 | * |
| 1023 | * // Try parsing as integer, fallback to string |
| 1024 | * const valueFlag = Flag.orElse( |
| 1025 | * Flag.Int("value"), |
| 1026 | * () => Flag.String("value") |
| 1027 | * ) |
| 1028 | * |
| 1029 | * // Multiple input sources with fallback |
| 1030 | * const configFlag = Flag.orElse( |
| 1031 | * Flag.File("config"), |
| 1032 | * () => Flag.String("config-url") |
| 1033 | * ) |
| 1034 | * const kinds = [valueFlag.kind, configFlag.kind] // => ["flag", "flag"] |
| 1035 | * ``` |
| 1036 | * |
| 1037 | * @stability unstable |
| 1038 | * @category alternatives |
| 1039 | * @since 4.0.0 |
| 1040 | */ |
| 1041 | export const orElse: { |
| 1042 | <B>(that: LazyArg<Flag<B>>): <A>(self: Flag<A>) => Flag<A | B> |
| 1043 | <A, B>(self: Flag<A>, that: LazyArg<Flag<B>>): Flag<A | B> |
| 1044 | } = dual(2, <A, B>(self: Flag<A>, that: LazyArg<Flag<B>>) => Param.orElse(self, that)) |
| 1045 | |
| 1046 | /** |
| 1047 | * Tries to parse with the first flag, then the second, returning a Result that indicates which succeeded. |
| 1048 | * |
| 1049 | * **Example** (Returning fallback results) |
| 1050 | * |
| 1051 | * ```ts import.meta.vitest |
| 1052 | * import { Effect, FileSystem, Layer, Path, Result, Stdio, Terminal } from "effect" |
| 1053 | * import { Flag } from "effect/cli" |
| 1054 | * import { ChildProcessSpawner } from "effect/process" |
| 1055 | * |
| 1056 | * const CliTestLayer = Layer.mergeAll( |
| 1057 | * FileSystem.layerNoop({}), |
| 1058 | * Path.layer, |
| 1059 | * Stdio.layerTest({}), |
| 1060 | * Layer.succeed(Terminal.Terminal, Terminal.make({ |
| 1061 | * columns: Effect.succeed(80), |
| 1062 | * rows: Effect.succeed(24), |
| 1063 | * readInput: Effect.die("unused"), |
| 1064 | * readLine: Effect.die("unused"), |
| 1065 | * display: () => Effect.void |
| 1066 | * })), |
| 1067 | * Layer.succeed( |
| 1068 | * ChildProcessSpawner.ChildProcessSpawner, |
| 1069 | * ChildProcessSpawner.make(() => Effect.die("unused")) |
| 1070 | * ) |
| 1071 | * ) |
| 1072 | * |
| 1073 | * const sourceFlag = Flag.orElseResult( |
| 1074 | * Flag.String("source"), |
| 1075 | * () => Flag.String("source-url") |
| 1076 | * ) |
| 1077 | * |
| 1078 | * const program = Effect.gen(function*() { |
| 1079 | * const [, source] = yield* sourceFlag.parse({ |
| 1080 | * arguments: [], |
| 1081 | * flags: { "source-url": ["https://example.com"] } |
| 1082 | * }) |
| 1083 | * return source |
| 1084 | * }) |
| 1085 | * |
| 1086 | * await Effect.runPromise(program.pipe(Effect.provide(CliTestLayer))) // => Result.fail("https://example.com") |
| 1087 | * ``` |
| 1088 | * |
| 1089 | * @stability unstable |
| 1090 | * @category alternatives |
| 1091 | * @since 4.0.0 |
| 1092 | */ |
| 1093 | export const orElseResult: { |
| 1094 | <B>(that: LazyArg<Flag<B>>): <A>(self: Flag<A>) => Flag<Result.Result<A, B>> |
| 1095 | <A, B>(self: Flag<A>, that: LazyArg<Flag<B>>): Flag<Result.Result<A, B>> |
| 1096 | } = dual(2, <A, B>(self: Flag<A>, that: LazyArg<Flag<B>>) => Param.orElseResult(self, that)) |
| 1097 | |
| 1098 | /** |
| 1099 | * Validates and transforms a flag value using a Schema codec. |
| 1100 | * |
| 1101 | * **Example** (Validating with schemas) |
| 1102 | * |
| 1103 | * ```ts import.meta.vitest |
| 1104 | * import { Schema } from "effect" |
| 1105 | * import { Flag } from "effect/cli" |
| 1106 | * |
| 1107 | * const isEmail = Schema.isPattern(/^[^\s@]+@[^\s@]+\.[^\s@]+$/, { |
| 1108 | * message: "Must be a valid email address" |
| 1109 | * }) |
| 1110 | * |
| 1111 | * // Parse and validate email with custom schema |
| 1112 | * const EmailSchema = Schema.String.pipe( |
| 1113 | * Schema.check(isEmail) |
| 1114 | * ) |
| 1115 | * |
| 1116 | * const emailFlag = Flag.String("email").pipe( |
| 1117 | * Flag.withSchema(EmailSchema) |
| 1118 | * ) |
| 1119 | * |
| 1120 | * // Parse JSON configuration with schema validation |
| 1121 | * const ConfigSchema = Schema.Struct({ |
| 1122 | * port: Schema.Number, |
| 1123 | * host: Schema.String, |
| 1124 | * ssl: Schema.optional(Schema.Boolean) |
| 1125 | * }).pipe(Schema.fromJsonString) |
| 1126 | * |
| 1127 | * const configFlag = Flag.String("config").pipe( |
| 1128 | * Flag.withSchema(ConfigSchema) |
| 1129 | * ) |
| 1130 | * const kinds = [emailFlag.kind, configFlag.kind] // => ["flag", "flag"] |
| 1131 | * ``` |
| 1132 | * |
| 1133 | * @stability unstable |
| 1134 | * @category schemas |
| 1135 | * @since 4.0.0 |
| 1136 | */ |
| 1137 | export const withSchema: { |
| 1138 | <A, B>(schema: Schema.ConstraintCodec<B, A, Environment, unknown>): (self: Flag<A>) => Flag<B> |
| 1139 | <A, B>(self: Flag<A>, schema: Schema.ConstraintCodec<B, A, Environment, unknown>): Flag<B> |
| 1140 | } = dual( |
| 1141 | 2, |
| 1142 | <A, B>(self: Flag<A>, schema: Schema.ConstraintCodec<B, A, Environment, unknown>) => Param.withSchema(self, schema) |
| 1143 | ) |