| 1 | /** |
| 2 | * Defines the shared parameter model for CLI arguments and flags. |
| 3 | * |
| 4 | * A `Param<Kind, A>` describes how to consume parsed command-line input and |
| 5 | * return a typed value. The `Kind` decides whether the parameter reads |
| 6 | * positional arguments or named flags. `Argument` and `Flag` build on this |
| 7 | * module to share parsing structure, primitive constructors, help metadata, |
| 8 | * aliases, defaults, prompts, configuration fallbacks, validation, schema |
| 9 | * decoding, fallback parameters, and traversal helpers. |
| 10 | * |
| 11 | * @stability unstable |
| 12 | * @since 4.0.0 |
| 13 | */ |
| 14 | import * as Config from "../Config.ts" |
| 15 | import * as Effect from "../Effect.ts" |
| 16 | import { dual, identity, type LazyArg } from "../Function.ts" |
| 17 | import * as Option from "../Option.ts" |
| 18 | import { type Pipeable, pipeArguments } from "../Pipeable.ts" |
| 19 | import * as Predicate from "../Predicate.ts" |
| 20 | import type * as Redacted_ from "../Redacted.ts" |
| 21 | import * as Result from "../Result.ts" |
| 22 | import * as Schema from "../Schema.ts" |
| 23 | import type { Covariant } from "../Types.ts" |
| 24 | import * as CliError from "./CliError.ts" |
| 25 | import type { Environment } from "./Command.ts" |
| 26 | import * as Primitive from "./Primitive.ts" |
| 27 | import * as Prompt from "./Prompt.ts" |
| 28 | |
| 29 | const TypeId = "~effect/cli/Param" |
| 30 | |
| 31 | /** |
| 32 | * Polymorphic CLI parameter shared by `Argument` and `Flag`. |
| 33 | * |
| 34 | * **Details** |
| 35 | * |
| 36 | * A parameter knows whether it consumes positional arguments or flags and |
| 37 | * parses a `ParsedArgs` value into its typed result. |
| 38 | * |
| 39 | * @stability unstable |
| 40 | * @category models |
| 41 | * @since 4.0.0 |
| 42 | */ |
| 43 | export interface Param<Kind extends ParamKind, out A> extends Param.Variance<A> { |
| 44 | readonly _tag: "Single" | "Map" | "Transform" | "Optional" | "Variadic" |
| 45 | readonly kind: Kind |
| 46 | readonly parse: Parse<A> |
| 47 | } |
| 48 | |
| 49 | /** |
| 50 | * Discriminator for whether a `Param` parses positional arguments or |
| 51 | * command-line flags. |
| 52 | * |
| 53 | * @stability unstable |
| 54 | * @category models |
| 55 | * @since 4.0.0 |
| 56 | */ |
| 57 | export type ParamKind = "argument" | "flag" |
| 58 | |
| 59 | /** |
| 60 | * Defines the kind discriminator for positional argument parameters. |
| 61 | * |
| 62 | * **When to use** |
| 63 | * |
| 64 | * Use to build low-level `Param` constructors or type positions for positional |
| 65 | * argument parameters. |
| 66 | * |
| 67 | * @see {@link flagKind} for the named flag parameter discriminator |
| 68 | * @see {@link ParamKind} for the full parameter kind union |
| 69 | * |
| 70 | * @stability unstable |
| 71 | * @category constants |
| 72 | * @since 4.0.0 |
| 73 | */ |
| 74 | export const argumentKind: "argument" = "argument" as const |
| 75 | |
| 76 | /** |
| 77 | * Defines the kind discriminator for flag parameters. |
| 78 | * |
| 79 | * **When to use** |
| 80 | * |
| 81 | * Use to build low-level `Param` constructors or type positions for named flag |
| 82 | * parameters. |
| 83 | * |
| 84 | * @see {@link argumentKind} for the positional argument parameter discriminator |
| 85 | * |
| 86 | * @stability unstable |
| 87 | * @category constants |
| 88 | * @since 4.0.0 |
| 89 | */ |
| 90 | export const flagKind: "flag" = "flag" as const |
| 91 | |
| 92 | /** |
| 93 | * Represents any parameter. |
| 94 | * |
| 95 | * @stability unstable |
| 96 | * @category models |
| 97 | * @since 4.0.0 |
| 98 | */ |
| 99 | export type Any = Param<ParamKind, unknown> |
| 100 | |
| 101 | /** |
| 102 | * Represents any positional argument parameter. |
| 103 | * |
| 104 | * @stability unstable |
| 105 | * @category models |
| 106 | * @since 4.0.0 |
| 107 | */ |
| 108 | export type AnyArgument = Param<typeof argumentKind, unknown> |
| 109 | |
| 110 | /** |
| 111 | * Represents any flag parameter. |
| 112 | * |
| 113 | * @stability unstable |
| 114 | * @category models |
| 115 | * @since 4.0.0 |
| 116 | */ |
| 117 | export type AnyFlag = Param<typeof flagKind, unknown> |
| 118 | |
| 119 | /** |
| 120 | * Function type used by parameters to parse currently available flags and |
| 121 | * positional arguments. |
| 122 | * |
| 123 | * **Details** |
| 124 | * |
| 125 | * It returns the remaining positional arguments together with the parsed value, |
| 126 | * or fails with a `CliError` while requiring the CLI parsing environment. |
| 127 | * |
| 128 | * @stability unstable |
| 129 | * @category models |
| 130 | * @since 4.0.0 |
| 131 | */ |
| 132 | export type Parse<A> = (args: ParsedArgs) => Effect.Effect< |
| 133 | readonly [leftover: ReadonlyArray<string>, value: A], |
| 134 | CliError.CliError, |
| 135 | Environment |
| 136 | > |
| 137 | |
| 138 | /** |
| 139 | * Namespace containing type-level utilities attached to the `Param` interface. |
| 140 | * |
| 141 | * @stability unstable |
| 142 | * @since 4.0.0 |
| 143 | */ |
| 144 | export declare namespace Param { |
| 145 | /** |
| 146 | * Variance and pipeability marker carried by every `Param` value. |
| 147 | * |
| 148 | * @stability unstable |
| 149 | * @category models |
| 150 | * @since 4.0.0 |
| 151 | */ |
| 152 | export interface Variance<out A> extends Pipeable { |
| 153 | readonly [TypeId]: { |
| 154 | readonly _A: Covariant<A> |
| 155 | } |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | /** |
| 160 | * Map of flag names to their provided string values. |
| 161 | * Multiple occurrences of a flag produce multiple values. |
| 162 | * |
| 163 | * @stability unstable |
| 164 | * @category models |
| 165 | * @since 4.0.0 |
| 166 | */ |
| 167 | export type Flags = Record<string, ReadonlyArray<string>> |
| 168 | |
| 169 | /** |
| 170 | * Input context passed to `Param.parse` implementations. |
| 171 | * - `flags`: already-collected flag values by canonical flag name |
| 172 | * - `arguments`: remaining positional arguments to be consumed |
| 173 | * |
| 174 | * @stability unstable |
| 175 | * @category models |
| 176 | * @since 4.0.0 |
| 177 | */ |
| 178 | export interface ParsedArgs { |
| 179 | readonly flags: Flags |
| 180 | readonly arguments: ReadonlyArray<string> |
| 181 | } |
| 182 | |
| 183 | /** |
| 184 | * Represents a fallback prompt that can either be provided directly or |
| 185 | * computed effectfully when the parameter is missing. |
| 186 | * |
| 187 | * @stability unstable |
| 188 | * @category models |
| 189 | * @since 4.0.0 |
| 190 | */ |
| 191 | export type FallbackPrompt<A> = |
| 192 | | Prompt.Prompt<A> |
| 193 | | Effect.Effect<Prompt.Prompt<A>, CliError.CliError, Environment> |
| 194 | |
| 195 | /** |
| 196 | * Leaf parameter that reads one named argument or flag with a primitive parser. |
| 197 | * |
| 198 | * **Details** |
| 199 | * |
| 200 | * Single parameters carry the user-facing name, aliases, description, primitive |
| 201 | * type, and optional metavar/type name used in help output. |
| 202 | * |
| 203 | * @stability unstable |
| 204 | * @category models |
| 205 | * @since 4.0.0 |
| 206 | */ |
| 207 | export interface Single<Kind extends ParamKind, out A> extends Param<Kind, A> { |
| 208 | readonly _tag: "Single" |
| 209 | readonly kind: Kind |
| 210 | readonly name: string |
| 211 | readonly description: Option.Option<string> |
| 212 | readonly aliases: ReadonlyArray<string> |
| 213 | readonly primitiveType: Primitive.Primitive<A> |
| 214 | readonly typeName?: string | undefined |
| 215 | readonly hidden: boolean |
| 216 | } |
| 217 | |
| 218 | /** |
| 219 | * Parameter node that maps the successfully parsed value of another parameter |
| 220 | * with a pure function. |
| 221 | * |
| 222 | * @stability unstable |
| 223 | * @category models |
| 224 | * @since 4.0.0 |
| 225 | */ |
| 226 | export interface Map<Kind extends ParamKind, in out A, out B> extends Param<Kind, B> { |
| 227 | readonly _tag: "Map" |
| 228 | readonly kind: Kind |
| 229 | readonly param: Param<Kind, A> |
| 230 | readonly f: (value: A) => B |
| 231 | } |
| 232 | |
| 233 | /** |
| 234 | * Parameter node that rewrites another parameter's parser, allowing effectful |
| 235 | * validation, fallback behavior, or error translation while preserving the same |
| 236 | * parameter kind. |
| 237 | * |
| 238 | * @stability unstable |
| 239 | * @category models |
| 240 | * @since 4.0.0 |
| 241 | */ |
| 242 | export interface Transform<Kind extends ParamKind, in out A, out B> extends Param<Kind, B> { |
| 243 | readonly _tag: "Transform" |
| 244 | readonly kind: Kind |
| 245 | readonly param: Param<Kind, A> |
| 246 | readonly alternatives: ReadonlyArray<LazyArg<Param<Kind, unknown>>> |
| 247 | readonly f: ( |
| 248 | parse: Parse<A>, |
| 249 | alternatives: ReadonlyArray<LazyArg<Parse<unknown>>> |
| 250 | ) => Parse<B> |
| 251 | } |
| 252 | |
| 253 | /** |
| 254 | * Parameter node that turns a missing argument or flag into `Option.none()` and |
| 255 | * a present parsed value into `Option.some(value)`. |
| 256 | * |
| 257 | * @stability unstable |
| 258 | * @category models |
| 259 | * @since 4.0.0 |
| 260 | */ |
| 261 | export interface Optional<Kind extends ParamKind, A> extends Param<Kind, Option.Option<A>> { |
| 262 | readonly _tag: "Optional" |
| 263 | readonly kind: Kind |
| 264 | readonly param: Param<Kind, A> |
| 265 | } |
| 266 | |
| 267 | /** |
| 268 | * Parameter node that parses another parameter zero or more times and returns |
| 269 | * all parsed values as an array, respecting optional minimum and maximum |
| 270 | * occurrence bounds. |
| 271 | * |
| 272 | * @stability unstable |
| 273 | * @category models |
| 274 | * @since 4.0.0 |
| 275 | */ |
| 276 | export interface Variadic<Kind extends ParamKind, A> extends Param<Kind, ReadonlyArray<A>> { |
| 277 | readonly _tag: "Variadic" |
| 278 | readonly kind: Kind |
| 279 | readonly param: Param<Kind, A> |
| 280 | readonly min: Option.Option<number> |
| 281 | readonly max: Option.Option<number> |
| 282 | } |
| 283 | |
| 284 | const Proto = { |
| 285 | [TypeId]: { |
| 286 | _A: identity |
| 287 | }, |
| 288 | pipe() { |
| 289 | return pipeArguments(this, arguments) |
| 290 | } |
| 291 | } |
| 292 | |
| 293 | /** |
| 294 | * Type guard to check if a value is a Param. |
| 295 | * |
| 296 | * **Example** (Checking for params) |
| 297 | * |
| 298 | * ```ts import.meta.vitest |
| 299 | * import { Param } from "effect/cli" |
| 300 | * |
| 301 | * const maybeParam = Param.String(Param.flagKind, "name") |
| 302 | * |
| 303 | * Param.isParam(maybeParam) // => true |
| 304 | * ``` |
| 305 | * |
| 306 | * @stability unstable |
| 307 | * @category guards |
| 308 | * @since 4.0.0 |
| 309 | */ |
| 310 | export const isParam = (u: unknown): u is Param<any, ParamKind> => Predicate.hasProperty(u, TypeId) |
| 311 | |
| 312 | /** |
| 313 | * Type guard to check if a param is a Single param (not composed). |
| 314 | * |
| 315 | * **Example** (Checking for single params) |
| 316 | * |
| 317 | * ```ts import.meta.vitest |
| 318 | * import { Param } from "effect/cli" |
| 319 | * |
| 320 | * const nameParam = Param.String(Param.flagKind, "name") |
| 321 | * const optionalParam = Param.optional(nameParam) |
| 322 | * |
| 323 | * Param.isSingle(nameParam) // => true |
| 324 | * Param.isSingle(optionalParam) // => false |
| 325 | * ``` |
| 326 | * |
| 327 | * @stability unstable |
| 328 | * @category guards |
| 329 | * @since 4.0.0 |
| 330 | */ |
| 331 | export const isSingle = <const Kind extends ParamKind, A>( |
| 332 | param: Param<Kind, A> |
| 333 | ): param is Single<Kind, A> => Predicate.isTagged(param, "Single") |
| 334 | |
| 335 | /** |
| 336 | * Type guard to check if a Single param is a flag (not an argument). |
| 337 | * |
| 338 | * @internal |
| 339 | */ |
| 340 | export const isFlagParam = <A>( |
| 341 | single: Single<ParamKind, A> |
| 342 | ): single is Single<typeof flagKind, A> => single.kind === "flag" |
| 343 | |
| 344 | /** |
| 345 | * Constructs a leaf `Single` parameter from its kind, name, primitive parser, |
| 346 | * and optional help metadata. |
| 347 | * |
| 348 | * **Details** |
| 349 | * |
| 350 | * The returned parser reads either one positional argument or the named flag, |
| 351 | * depending on `kind`. |
| 352 | * |
| 353 | * @stability unstable |
| 354 | * @category constructors |
| 355 | * @since 4.0.0 |
| 356 | */ |
| 357 | export const makeSingle = <const Kind extends ParamKind, A>(params: { |
| 358 | readonly kind: Kind |
| 359 | readonly name: string |
| 360 | readonly primitiveType: Primitive.Primitive<A> |
| 361 | readonly typeName?: string | undefined |
| 362 | readonly description?: Option.Option<string> | undefined |
| 363 | readonly aliases?: ReadonlyArray<string> | undefined |
| 364 | readonly hidden?: boolean | undefined |
| 365 | }): Single<Kind, A> => { |
| 366 | const parse: Parse<A> = (args) => |
| 367 | params.kind === argumentKind |
| 368 | ? parsePositional(params.name, params.primitiveType, args) |
| 369 | : parseFlag(params.name, params.primitiveType, args) |
| 370 | return Object.setPrototypeOf({ |
| 371 | _tag: "Single", |
| 372 | ...params, |
| 373 | description: params.description ?? Option.none(), |
| 374 | aliases: params.aliases ?? [], |
| 375 | hidden: params.hidden ?? false, |
| 376 | parse |
| 377 | }, Proto) |
| 378 | } |
| 379 | |
| 380 | /** |
| 381 | * Creates a string parameter. |
| 382 | * |
| 383 | * **Example** (Creating string parameters) |
| 384 | * |
| 385 | * ```ts import.meta.vitest |
| 386 | * import { Param } from "effect/cli" |
| 387 | * |
| 388 | * // Create a string flag |
| 389 | * const nameFlag = Param.String(Param.flagKind, "name") |
| 390 | * |
| 391 | * // Create a string argument |
| 392 | * const fileArg = Param.String(Param.argumentKind, "file") |
| 393 | * |
| 394 | * // Usage in CLI: --name "John Doe" or as positional argument |
| 395 | * const kinds = [nameFlag.kind, fileArg.kind] // => ["flag", "argument"] |
| 396 | * ``` |
| 397 | * |
| 398 | * @stability unstable |
| 399 | * @category constructors |
| 400 | * @since 4.0.0 |
| 401 | */ |
| 402 | export const String = <const Kind extends ParamKind>( |
| 403 | kind: Kind, |
| 404 | name: string |
| 405 | ): Param<Kind, string> => |
| 406 | makeSingle({ |
| 407 | name, |
| 408 | primitiveType: Primitive.String, |
| 409 | kind |
| 410 | }) |
| 411 | |
| 412 | /** |
| 413 | * Creates a boolean parameter. |
| 414 | * |
| 415 | * **Example** (Creating boolean parameters) |
| 416 | * |
| 417 | * ```ts import.meta.vitest |
| 418 | * import { Param } from "effect/cli" |
| 419 | * |
| 420 | * // Create a boolean flag |
| 421 | * const verboseFlag = Param.Boolean(Param.flagKind, "verbose") |
| 422 | * |
| 423 | * // Create a boolean argument |
| 424 | * const enableArg = Param.Boolean(Param.argumentKind, "enable") |
| 425 | * |
| 426 | * // Usage in CLI: --verbose (true) or --no-verbose (false). |
| 427 | * // The flag is required unless made optional or given a fallback. |
| 428 | * // Boolean positional arguments accept true/false. |
| 429 | * const kinds = [verboseFlag.kind, enableArg.kind] // => ["flag", "argument"] |
| 430 | * ``` |
| 431 | * |
| 432 | * @stability unstable |
| 433 | * @category constructors |
| 434 | * @since 4.0.0 |
| 435 | */ |
| 436 | export const Boolean = <const Kind extends ParamKind>( |
| 437 | kind: Kind, |
| 438 | name: string |
| 439 | ): Param<Kind, boolean> => |
| 440 | makeSingle({ |
| 441 | name, |
| 442 | primitiveType: Primitive.Boolean, |
| 443 | kind |
| 444 | }) |
| 445 | |
| 446 | /** |
| 447 | * Creates an integer parameter. |
| 448 | * |
| 449 | * **Example** (Creating integer parameters) |
| 450 | * |
| 451 | * ```ts import.meta.vitest |
| 452 | * import { Param } from "effect/cli" |
| 453 | * |
| 454 | * // Create an integer flag |
| 455 | * const portFlag = Param.Int(Param.flagKind, "port") |
| 456 | * |
| 457 | * // Create an integer argument |
| 458 | * const countArg = Param.Int(Param.argumentKind, "count") |
| 459 | * |
| 460 | * // Usage in CLI: --port 8080 or as positional argument: 42 |
| 461 | * const kinds = [portFlag.kind, countArg.kind] // => ["flag", "argument"] |
| 462 | * ``` |
| 463 | * |
| 464 | * @stability unstable |
| 465 | * @category constructors |
| 466 | * @since 4.0.0 |
| 467 | */ |
| 468 | export const Int = <const Kind extends ParamKind>( |
| 469 | kind: Kind, |
| 470 | name: string |
| 471 | ): Param<Kind, number> => |
| 472 | makeSingle({ |
| 473 | name, |
| 474 | primitiveType: Primitive.Int, |
| 475 | kind |
| 476 | }) |
| 477 | |
| 478 | /** |
| 479 | * Creates a finite number parameter. |
| 480 | * |
| 481 | * **Example** (Creating finite number parameters) |
| 482 | * |
| 483 | * ```ts import.meta.vitest |
| 484 | * import { Param } from "effect/cli" |
| 485 | * |
| 486 | * const rateFlag = Param.Finite(Param.flagKind, "rate") |
| 487 | * |
| 488 | * const thresholdArg = Param.Finite(Param.argumentKind, "threshold") |
| 489 | * |
| 490 | * const kinds = [rateFlag.kind, thresholdArg.kind] // => ["flag", "argument"] |
| 491 | * ``` |
| 492 | * |
| 493 | * @stability unstable |
| 494 | * @category constructors |
| 495 | * @since 4.0.0 |
| 496 | */ |
| 497 | export const Finite = <const Kind extends ParamKind>( |
| 498 | kind: Kind, |
| 499 | name: string |
| 500 | ): Param<Kind, number> => |
| 501 | makeSingle({ |
| 502 | name, |
| 503 | primitiveType: Primitive.Finite, |
| 504 | kind |
| 505 | }) |
| 506 | |
| 507 | /** |
| 508 | * Creates a date parameter that parses ISO date strings. |
| 509 | * |
| 510 | * **Example** (Creating date parameters) |
| 511 | * |
| 512 | * ```ts import.meta.vitest |
| 513 | * import { Param } from "effect/cli" |
| 514 | * |
| 515 | * // Create a date flag |
| 516 | * const startFlag = Param.Date(Param.flagKind, "start-date") |
| 517 | * |
| 518 | * // Create a date argument |
| 519 | * const dueDateArg = Param.Date(Param.argumentKind, "due-date") |
| 520 | * |
| 521 | * // Usage in CLI: --start-date "2023-12-25" or as positional: "2023-01-01" |
| 522 | * // Parses to JavaScript Date object |
| 523 | * const kinds = [startFlag.kind, dueDateArg.kind] // => ["flag", "argument"] |
| 524 | * ``` |
| 525 | * |
| 526 | * @stability unstable |
| 527 | * @category constructors |
| 528 | * @since 4.0.0 |
| 529 | */ |
| 530 | export const Date = <const Kind extends ParamKind>( |
| 531 | kind: Kind, |
| 532 | name: string |
| 533 | ): Param<Kind, globalThis.Date> => |
| 534 | makeSingle({ |
| 535 | name, |
| 536 | primitiveType: Primitive.Date, |
| 537 | kind |
| 538 | }) |
| 539 | |
| 540 | /** |
| 541 | * Constructs command-line params that represent a choice between several |
| 542 | * inputs. The input will be mapped to it's associated value during parsing. |
| 543 | * |
| 544 | * **Example** (Creating valued choices) |
| 545 | * |
| 546 | * ```ts import.meta.vitest |
| 547 | * import { Param } from "effect/cli" |
| 548 | * |
| 549 | * type Animal = Dog | Cat |
| 550 | * |
| 551 | * interface Dog { |
| 552 | * readonly _tag: "Dog" |
| 553 | * } |
| 554 | * |
| 555 | * interface Cat { |
| 556 | * readonly _tag: "Cat" |
| 557 | * } |
| 558 | * |
| 559 | * const animal = Param.ChoiceWithValue(Param.flagKind, "animal", [ |
| 560 | * ["dog", { _tag: "Dog" }], |
| 561 | * ["cat", { _tag: "Cat" }] |
| 562 | * ]) |
| 563 | * animal.kind // => "flag" |
| 564 | * ``` |
| 565 | * |
| 566 | * @stability unstable |
| 567 | * @category constructors |
| 568 | * @since 4.0.0 |
| 569 | */ |
| 570 | export const ChoiceWithValue = < |
| 571 | const Kind extends ParamKind, |
| 572 | const Choices extends ReadonlyArray<readonly [string, any]> |
| 573 | >(kind: Kind, name: string, choices: Choices): Param<Kind, Choices[number][1]> => |
| 574 | makeSingle({ |
| 575 | name, |
| 576 | primitiveType: Primitive.Choice(choices), |
| 577 | kind |
| 578 | }) |
| 579 | |
| 580 | /** |
| 581 | * Constructs command-line params that represent a choice between several |
| 582 | * string inputs. |
| 583 | * |
| 584 | * **Example** (Creating string choices) |
| 585 | * |
| 586 | * ```ts import.meta.vitest |
| 587 | * import { Param } from "effect/cli" |
| 588 | * |
| 589 | * const logLevel = Param.Literals(Param.flagKind, "log-level", [ |
| 590 | * "debug", |
| 591 | * "info", |
| 592 | * "warn", |
| 593 | * "error" |
| 594 | * ]) |
| 595 | * logLevel.kind // => "flag" |
| 596 | * ``` |
| 597 | * |
| 598 | * @stability unstable |
| 599 | * @category constructors |
| 600 | * @since 4.0.0 |
| 601 | */ |
| 602 | export const Literals = < |
| 603 | const Kind extends ParamKind, |
| 604 | const Choices extends ReadonlyArray<string> |
| 605 | >(kind: Kind, name: string, choices: Choices): Param<Kind, Choices[number]> => { |
| 606 | const mappedChoices = choices.map((value) => [value, value] as const) |
| 607 | return ChoiceWithValue(kind, name, mappedChoices) |
| 608 | } |
| 609 | |
| 610 | /** |
| 611 | * Creates a path parameter that accepts file or directory paths. |
| 612 | * |
| 613 | * **Example** (Creating path parameters) |
| 614 | * |
| 615 | * ```ts import.meta.vitest |
| 616 | * import { Param } from "effect/cli" |
| 617 | * |
| 618 | * // Basic path parameter |
| 619 | * const outputPath = Param.Path(Param.flagKind, "output") |
| 620 | * |
| 621 | * // Path that must exist |
| 622 | * const inputPath = Param.Path(Param.flagKind, "input", { mustExist: true }) |
| 623 | * |
| 624 | * // File-only path |
| 625 | * const configFile = Param.Path(Param.flagKind, "config", { |
| 626 | * pathType: "file", |
| 627 | * mustExist: true, |
| 628 | * typeName: "config-file" |
| 629 | * }) |
| 630 | * const kinds = [outputPath.kind, inputPath.kind, configFile.kind] // => ["flag", "flag", "flag"] |
| 631 | * ``` |
| 632 | * |
| 633 | * @stability unstable |
| 634 | * @category constructors |
| 635 | * @since 4.0.0 |
| 636 | */ |
| 637 | export const Path = <Kind extends ParamKind>( |
| 638 | kind: Kind, |
| 639 | name: string, |
| 640 | options?: { |
| 641 | readonly pathType?: Primitive.PathType | undefined |
| 642 | readonly mustExist?: boolean | undefined |
| 643 | readonly typeName?: string | undefined |
| 644 | } |
| 645 | ): Param<Kind, string> => |
| 646 | makeSingle({ |
| 647 | name, |
| 648 | kind, |
| 649 | primitiveType: Primitive.Path(options?.pathType ?? "either", options?.mustExist), |
| 650 | typeName: options?.typeName |
| 651 | }) |
| 652 | |
| 653 | /** |
| 654 | * Creates a directory path parameter. |
| 655 | * |
| 656 | * **Details** |
| 657 | * |
| 658 | * This is a convenience function that creates a path parameter with the |
| 659 | * `pathType` set to `"directory"` and a default type name of `"directory"`. |
| 660 | * |
| 661 | * **Example** (Creating directory parameters) |
| 662 | * |
| 663 | * ```ts import.meta.vitest |
| 664 | * import { Param } from "effect/cli" |
| 665 | * |
| 666 | * // Basic directory parameter |
| 667 | * const outputDir = Param.Directory(Param.flagKind, "output-dir") |
| 668 | * |
| 669 | * // Directory that must exist |
| 670 | * const sourceDir = Param.Directory(Param.flagKind, "source", { mustExist: true }) |
| 671 | * |
| 672 | * // Usage: --output-dir /path/to/dir --source /existing/dir |
| 673 | * const kinds = [outputDir.kind, sourceDir.kind] // => ["flag", "flag"] |
| 674 | * ``` |
| 675 | * |
| 676 | * @stability unstable |
| 677 | * @category constructors |
| 678 | * @since 4.0.0 |
| 679 | */ |
| 680 | export const Directory = <Kind extends ParamKind>( |
| 681 | kind: Kind, |
| 682 | name: string, |
| 683 | options?: { |
| 684 | readonly mustExist?: boolean | undefined |
| 685 | } |
| 686 | ): Param<Kind, string> => |
| 687 | Path(kind, name, { |
| 688 | pathType: "directory", |
| 689 | typeName: "directory", |
| 690 | mustExist: options?.mustExist |
| 691 | }) |
| 692 | |
| 693 | /** |
| 694 | * Creates a file path parameter. |
| 695 | * |
| 696 | * **Details** |
| 697 | * |
| 698 | * This is a convenience function that creates a path parameter with a |
| 699 | * `pathType` set to `"file"` and a default type name of `"file"`. |
| 700 | * |
| 701 | * **Example** (Creating file parameters) |
| 702 | * |
| 703 | * ```ts import.meta.vitest |
| 704 | * import { Param } from "effect/cli" |
| 705 | * |
| 706 | * // Basic file parameter |
| 707 | * const outputFile = Param.File(Param.flagKind, "output") |
| 708 | * |
| 709 | * // File that must exist |
| 710 | * const inputFile = Param.File(Param.flagKind, "input", { mustExist: true }) |
| 711 | * |
| 712 | * // Usage: --output result.txt --input existing-file.txt |
| 713 | * const kinds = [outputFile.kind, inputFile.kind] // => ["flag", "flag"] |
| 714 | * ``` |
| 715 | * |
| 716 | * @stability unstable |
| 717 | * @category constructors |
| 718 | * @since 4.0.0 |
| 719 | */ |
| 720 | export const File = <Kind extends ParamKind>( |
| 721 | kind: Kind, |
| 722 | name: string, |
| 723 | options?: { |
| 724 | readonly mustExist?: boolean | undefined |
| 725 | } |
| 726 | ): Param<Kind, string> => |
| 727 | Path(kind, name, { |
| 728 | pathType: "file", |
| 729 | typeName: "file", |
| 730 | mustExist: options?.mustExist |
| 731 | }) |
| 732 | |
| 733 | /** |
| 734 | * Creates a redacted parameter for sensitive data like passwords. |
| 735 | * The value is masked in help output and logging. |
| 736 | * |
| 737 | * **Example** (Creating redacted parameters) |
| 738 | * |
| 739 | * ```ts import.meta.vitest |
| 740 | * import { Param } from "effect/cli" |
| 741 | * |
| 742 | * // Create a password parameter |
| 743 | * const password = Param.Redacted(Param.flagKind, "password") |
| 744 | * |
| 745 | * // Create an API key argument |
| 746 | * const apiKey = Param.Redacted(Param.argumentKind, "api-key") |
| 747 | * |
| 748 | * // Usage: --password (value will be hidden in help/logs) |
| 749 | * const kinds = [password.kind, apiKey.kind] // => ["flag", "argument"] |
| 750 | * ``` |
| 751 | * |
| 752 | * @stability unstable |
| 753 | * @category constructors |
| 754 | * @since 4.0.0 |
| 755 | */ |
| 756 | export const Redacted = <Kind extends ParamKind>( |
| 757 | kind: Kind, |
| 758 | name: string |
| 759 | ): Param<Kind, Redacted_.Redacted<string>> => |
| 760 | makeSingle({ |
| 761 | name, |
| 762 | primitiveType: Primitive.Redacted, |
| 763 | kind |
| 764 | }) |
| 765 | |
| 766 | /** |
| 767 | * Creates a parameter that reads and returns file content as a string. |
| 768 | * |
| 769 | * **Example** (Reading file text) |
| 770 | * |
| 771 | * ```ts import.meta.vitest |
| 772 | * import { Param } from "effect/cli" |
| 773 | * |
| 774 | * // Read a config file as string |
| 775 | * const configContent = Param.FileText(Param.flagKind, "config") |
| 776 | * |
| 777 | * // Read a template file as argument |
| 778 | * const templateContent = Param.FileText(Param.argumentKind, "template") |
| 779 | * |
| 780 | * // Usage: --config config.txt (reads file content into string) |
| 781 | * const kinds = [configContent.kind, templateContent.kind] // => ["flag", "argument"] |
| 782 | * ``` |
| 783 | * |
| 784 | * @stability unstable |
| 785 | * @category constructors |
| 786 | * @since 4.0.0 |
| 787 | */ |
| 788 | export const FileText = <Kind extends ParamKind>(kind: Kind, name: string): Param<Kind, string> => |
| 789 | makeSingle({ |
| 790 | name, |
| 791 | primitiveType: Primitive.FileText, |
| 792 | kind |
| 793 | }) |
| 794 | |
| 795 | /** |
| 796 | * Creates a param that reads and parses the content of the specified file. |
| 797 | * |
| 798 | * **Details** |
| 799 | * |
| 800 | * The parser that is utilized will depend on the specified `format`, or the |
| 801 | * extension of the file passed on the command-line if no `format` is specified. |
| 802 | * |
| 803 | * **Example** (Parsing file contents) |
| 804 | * |
| 805 | * ```ts import.meta.vitest |
| 806 | * import { Param } from "effect/cli" |
| 807 | * |
| 808 | * // Will use the extension of the file passed on the command line to determine |
| 809 | * // the parser to use |
| 810 | * const config = Param.FileParse(Param.flagKind, "config") |
| 811 | * |
| 812 | * // Will use the JSON parser |
| 813 | * const jsonConfig = Param.FileParse(Param.flagKind, "json-config", { |
| 814 | * format: "json" |
| 815 | * }) |
| 816 | * const kinds = [config.kind, jsonConfig.kind] // => ["flag", "flag"] |
| 817 | * ``` |
| 818 | * |
| 819 | * @stability unstable |
| 820 | * @category constructors |
| 821 | * @since 4.0.0 |
| 822 | */ |
| 823 | export const FileParse = <Kind extends ParamKind>( |
| 824 | kind: Kind, |
| 825 | name: string, |
| 826 | options?: Primitive.FileParseOptions | undefined |
| 827 | ): Param<Kind, unknown> => |
| 828 | makeSingle({ |
| 829 | name, |
| 830 | primitiveType: Primitive.FileParse(options), |
| 831 | kind |
| 832 | }) |
| 833 | |
| 834 | /** |
| 835 | * Creates a parameter that reads and validates file content using a schema. |
| 836 | * |
| 837 | * **Example** (Validating file contents) |
| 838 | * |
| 839 | * ```ts import.meta.vitest |
| 840 | * import { Schema } from "effect" |
| 841 | * import { Param } from "effect/cli" |
| 842 | * // Parse JSON config file |
| 843 | * const configSchema = Schema.Struct({ |
| 844 | * port: Schema.Number, |
| 845 | * host: Schema.String |
| 846 | * }) |
| 847 | * |
| 848 | * const config = Param.FileSchema(Param.flagKind, "config", configSchema, { |
| 849 | * format: "json" |
| 850 | * }) |
| 851 | * |
| 852 | * // Parse YAML file |
| 853 | * const yamlConfig = Param.FileSchema(Param.flagKind, "config", configSchema, { |
| 854 | * format: "yaml" |
| 855 | * }) |
| 856 | * |
| 857 | * // Usage: --config config.json (reads and validates file content) |
| 858 | * const kinds = [config.kind, yamlConfig.kind] // => ["flag", "flag"] |
| 859 | * ``` |
| 860 | * |
| 861 | * @stability unstable |
| 862 | * @category constructors |
| 863 | * @since 4.0.0 |
| 864 | */ |
| 865 | export const FileSchema = <Kind extends ParamKind, A>( |
| 866 | kind: Kind, |
| 867 | name: string, |
| 868 | schema: Schema.ConstraintDecoder<A, Environment>, |
| 869 | options?: Primitive.FileSchemaOptions | undefined |
| 870 | ): Param<Kind, A> => |
| 871 | makeSingle({ |
| 872 | name, |
| 873 | primitiveType: Primitive.FileSchema(schema, options), |
| 874 | kind |
| 875 | }) |
| 876 | |
| 877 | /** |
| 878 | * Creates a param that parses key=value pairs. |
| 879 | * |
| 880 | * **When to use** |
| 881 | * |
| 882 | * Use when you need command-line options or arguments that collect `key=value` |
| 883 | * configuration entries. |
| 884 | * |
| 885 | * **Details** |
| 886 | * |
| 887 | * Requires at least one key=value pair. The parsed pairs are merged into a |
| 888 | * single record object. |
| 889 | * |
| 890 | * **Example** (Parsing key-value pairs) |
| 891 | * |
| 892 | * ```ts import.meta.vitest |
| 893 | * import { Param } from "effect/cli" |
| 894 | * |
| 895 | * const env = Param.KeyValuePair(Param.flagKind, "env") |
| 896 | * // --env FOO=bar --env BAZ=qux will parse to { FOO: "bar", BAZ: "qux" } |
| 897 | * |
| 898 | * const props = Param.KeyValuePair(Param.flagKind, "property") |
| 899 | * // --property name=value --property debug=true |
| 900 | * const kinds = [env.kind, props.kind] // => ["flag", "flag"] |
| 901 | * ``` |
| 902 | * |
| 903 | * @stability unstable |
| 904 | * @category constructors |
| 905 | * @since 4.0.0 |
| 906 | */ |
| 907 | export const KeyValuePair = <Kind extends ParamKind>( |
| 908 | kind: Kind, |
| 909 | name: string |
| 910 | ): Param<Kind, Record<string, string>> => |
| 911 | map( |
| 912 | variadic( |
| 913 | makeSingle({ |
| 914 | name, |
| 915 | primitiveType: Primitive.KeyValuePair, |
| 916 | kind |
| 917 | }), |
| 918 | { min: 1 } |
| 919 | ), |
| 920 | (objects) => Object.fromEntries(objects.flatMap(Object.entries)) |
| 921 | ) |
| 922 | |
| 923 | /** |
| 924 | * A parameter that always fails to parse. |
| 925 | * |
| 926 | * **Example** (Creating sentinel parameters) |
| 927 | * |
| 928 | * ```ts import.meta.vitest |
| 929 | * import { Param } from "effect/cli" |
| 930 | * |
| 931 | * const disabledDebugParam = Param.Never(Param.flagKind) |
| 932 | * |
| 933 | * const makeDebugParam = (enableDebug: boolean) => |
| 934 | * enableDebug ? Param.String(Param.flagKind, "debug") : disabledDebugParam |
| 935 | * |
| 936 | * makeDebugParam(true) === disabledDebugParam // => false |
| 937 | * makeDebugParam(false) === disabledDebugParam // => true |
| 938 | * ``` |
| 939 | * |
| 940 | * @stability unstable |
| 941 | * @category constructors |
| 942 | * @since 4.0.0 |
| 943 | */ |
| 944 | export const Never = <Kind extends ParamKind>(kind: Kind): Param<Kind, never> => |
| 945 | makeSingle({ |
| 946 | name: "__never__", |
| 947 | primitiveType: Primitive.Never, |
| 948 | kind |
| 949 | }) |
| 950 | |
| 951 | const FLAG_DASH_REGEXP = /^-+/ |
| 952 | |
| 953 | /** |
| 954 | * Adds an alias to an option. |
| 955 | * |
| 956 | * **When to use** |
| 957 | * |
| 958 | * Use when you need a CLI parameter to accept an alternate name, such as "-f" |
| 959 | * for "--force". |
| 960 | * |
| 961 | * **Details** |
| 962 | * |
| 963 | * This works on any param structure by recursively finding the underlying |
| 964 | * `Single` node and applying the alias there. |
| 965 | * |
| 966 | * **Example** (Adding parameter aliases) |
| 967 | * |
| 968 | * ```ts import.meta.vitest |
| 969 | * import { Param } from "effect/cli" |
| 970 | * |
| 971 | * const force = Param.Boolean(Param.flagKind, "force").pipe( |
| 972 | * Param.withAlias("-f"), |
| 973 | * Param.withAlias("-F") |
| 974 | * ) |
| 975 | * |
| 976 | * // Also works on composed params: |
| 977 | * const count = Param.Int(Param.flagKind, "count").pipe( |
| 978 | * Param.optional, |
| 979 | * Param.withAlias("-c") // finds the underlying Single and adds alias |
| 980 | * ) |
| 981 | * const kinds = [force.kind, count.kind] // => ["flag", "flag"] |
| 982 | * ``` |
| 983 | * |
| 984 | * @stability unstable |
| 985 | * @category combinators |
| 986 | * @since 4.0.0 |
| 987 | */ |
| 988 | export const withAlias: { |
| 989 | <Kind extends ParamKind, A>(alias: string): (self: Param<Kind, A>) => Param<Kind, A> |
| 990 | <Kind extends ParamKind, A>(self: Param<Kind, A>, alias: string): Param<Kind, A> |
| 991 | } = dual(2, <Kind extends ParamKind, A>(self: Param<Kind, A>, alias: string) => { |
| 992 | return transformSingle(self, <X>(single: Single<Kind, X>) => |
| 993 | makeSingle({ |
| 994 | ...single, |
| 995 | aliases: [...single.aliases, alias.replace(FLAG_DASH_REGEXP, "")] |
| 996 | })) |
| 997 | }) |
| 998 | |
| 999 | /** |
| 1000 | * Adds a description to an option for help text. |
| 1001 | * |
| 1002 | * **Details** |
| 1003 | * |
| 1004 | * Descriptions provide users with information about what the option does |
| 1005 | * when they view help documentation. |
| 1006 | * |
| 1007 | * **Example** (Adding help descriptions) |
| 1008 | * |
| 1009 | * ```ts import.meta.vitest |
| 1010 | * import { Param } from "effect/cli" |
| 1011 | * |
| 1012 | * const verbose = Param.Boolean(Param.flagKind, "verbose").pipe( |
| 1013 | * Param.withAlias("-v"), |
| 1014 | * Param.withDescription("Enable verbose output") |
| 1015 | * ) |
| 1016 | * verbose.kind // => "flag" |
| 1017 | * ``` |
| 1018 | * |
| 1019 | * @stability unstable |
| 1020 | * @category combinators |
| 1021 | * @since 4.0.0 |
| 1022 | */ |
| 1023 | export const withDescription: { |
| 1024 | <Kind extends ParamKind, A>(description: string): (self: Param<Kind, A>) => Param<Kind, A> |
| 1025 | <Kind extends ParamKind, A>(self: Param<Kind, A>, description: string): Param<Kind, A> |
| 1026 | } = dual(2, <Kind extends ParamKind, A>(self: Param<Kind, A>, description: string) => { |
| 1027 | return transformSingle(self, <X>(single: Single<Kind, X>) => |
| 1028 | makeSingle({ |
| 1029 | ...single, |
| 1030 | description: Option.some(description) |
| 1031 | })) |
| 1032 | }) |
| 1033 | |
| 1034 | /** |
| 1035 | * Hides a parameter from generated help output and completions while keeping |
| 1036 | * it parseable on the command line. |
| 1037 | * |
| 1038 | * **When to use** |
| 1039 | * |
| 1040 | * Use when experimental, internal, or deprecated flags should be accepted but |
| 1041 | * not advertised. |
| 1042 | * |
| 1043 | * **Example** (Hiding a flag from help) |
| 1044 | * |
| 1045 | * ```ts import.meta.vitest |
| 1046 | * import { Param } from "effect/cli" |
| 1047 | * |
| 1048 | * const experimental = Param.Boolean(Param.flagKind, "experimental-foo").pipe( |
| 1049 | * Param.withHidden |
| 1050 | * ) |
| 1051 | * experimental.kind // => "flag" |
| 1052 | * ``` |
| 1053 | * |
| 1054 | * @stability unstable |
| 1055 | * @category metadata |
| 1056 | * @since 4.0.0 |
| 1057 | */ |
| 1058 | export const withHidden = <Kind extends ParamKind, A>(self: Param<Kind, A>): Param<Kind, A> => |
| 1059 | transformSingle(self, <X>(single: Single<Kind, X>) => |
| 1060 | makeSingle({ |
| 1061 | ...single, |
| 1062 | hidden: true |
| 1063 | })) |
| 1064 | |
| 1065 | /** |
| 1066 | * Transforms the parsed value of an option using a mapping function. |
| 1067 | * |
| 1068 | * **Example** (Mapping parsed values) |
| 1069 | * |
| 1070 | * ```ts import.meta.vitest |
| 1071 | * import { Param } from "effect/cli" |
| 1072 | * |
| 1073 | * const port = Param.Int(Param.flagKind, "port").pipe( |
| 1074 | * Param.map((n) => ({ port: n, url: `http://localhost:${n}` })) |
| 1075 | * ) |
| 1076 | * port.kind // => "flag" |
| 1077 | * ``` |
| 1078 | * |
| 1079 | * @stability unstable |
| 1080 | * @category combinators |
| 1081 | * @since 4.0.0 |
| 1082 | */ |
| 1083 | export const map: { |
| 1084 | <A, B>(f: (a: A) => B): <Kind extends ParamKind>(self: Param<Kind, A>) => Param<Kind, B> |
| 1085 | <Kind extends ParamKind, A, B>(self: Param<Kind, A>, f: (a: A) => B): Param<Kind, B> |
| 1086 | } = dual(2, <Kind extends ParamKind, A, B>(self: Param<Kind, A>, f: (a: A) => B) => { |
| 1087 | const parse: Parse<B> = (args: ParsedArgs) => |
| 1088 | Effect.map( |
| 1089 | self.parse(args), |
| 1090 | ([operands, value]) => [operands, f(value)] as const |
| 1091 | ) |
| 1092 | return Object.assign(Object.create(Proto), { |
| 1093 | _tag: "Map", |
| 1094 | kind: self.kind, |
| 1095 | param: self, |
| 1096 | f, |
| 1097 | parse |
| 1098 | }) |
| 1099 | }) |
| 1100 | |
| 1101 | const transform = <Kind extends ParamKind, A, B>( |
| 1102 | self: Param<Kind, A>, |
| 1103 | f: ( |
| 1104 | parse: Parse<A>, |
| 1105 | alternatives: ReadonlyArray<LazyArg<Parse<unknown>>> |
| 1106 | ) => Parse<B>, |
| 1107 | alternatives: ReadonlyArray<LazyArg<Param<Kind, unknown>>> = [] |
| 1108 | ): Transform<Kind, A, B> => { |
| 1109 | const alternativeParsers = alternatives.map((alternative) => () => alternative().parse) |
| 1110 | return Object.assign(Object.create(Proto), { |
| 1111 | _tag: "Transform", |
| 1112 | kind: self.kind, |
| 1113 | param: self, |
| 1114 | alternatives, |
| 1115 | f, |
| 1116 | parse: f(self.parse, alternativeParsers) |
| 1117 | }) |
| 1118 | } |
| 1119 | |
| 1120 | /** |
| 1121 | * Transforms the parsed value of an option using an effectful mapping function. |
| 1122 | * |
| 1123 | * **Example** (Mapping parsed values effectfully) |
| 1124 | * |
| 1125 | * ```ts import.meta.vitest |
| 1126 | * import { Effect, FileSystem, Layer, Path, Stdio, Terminal } from "effect" |
| 1127 | * import { CliError, Param } from "effect/cli" |
| 1128 | * import { ChildProcessSpawner } from "effect/process" |
| 1129 | * |
| 1130 | * const CliTestLayer = Layer.mergeAll( |
| 1131 | * FileSystem.layerNoop({}), |
| 1132 | * Path.layer, |
| 1133 | * Stdio.layerTest({}), |
| 1134 | * Layer.succeed(Terminal.Terminal, Terminal.make({ |
| 1135 | * columns: Effect.succeed(80), |
| 1136 | * rows: Effect.succeed(24), |
| 1137 | * readInput: Effect.die("unused"), |
| 1138 | * readLine: Effect.die("unused"), |
| 1139 | * display: () => Effect.void |
| 1140 | * })), |
| 1141 | * Layer.succeed( |
| 1142 | * ChildProcessSpawner.ChildProcessSpawner, |
| 1143 | * ChildProcessSpawner.make(() => Effect.die("unused")) |
| 1144 | * ) |
| 1145 | * ) |
| 1146 | * |
| 1147 | * const validatedEmail = Param.String(Param.flagKind, "email").pipe( |
| 1148 | * Param.mapEffect((email) => |
| 1149 | * email.includes("@") |
| 1150 | * ? Effect.succeed(email) |
| 1151 | * : Effect.fail( |
| 1152 | * new CliError.InvalidValue({ |
| 1153 | * option: "email", |
| 1154 | * value: email, |
| 1155 | * expected: "valid email format", |
| 1156 | * kind: "flag" |
| 1157 | * }) |
| 1158 | * ) |
| 1159 | * ) |
| 1160 | * ) |
| 1161 | * |
| 1162 | * const [, value] = await Effect.runPromise( |
| 1163 | * validatedEmail.parse({ |
| 1164 | * arguments: [], |
| 1165 | * flags: { email: ["alice@example.com"] } |
| 1166 | * }).pipe(Effect.provide(CliTestLayer)) |
| 1167 | * ) |
| 1168 | * value // => "alice@example.com" |
| 1169 | * ``` |
| 1170 | * |
| 1171 | * @stability unstable |
| 1172 | * @category combinators |
| 1173 | * @since 4.0.0 |
| 1174 | */ |
| 1175 | export const mapEffect: { |
| 1176 | <A, B>( |
| 1177 | f: (a: A) => Effect.Effect<B, CliError.CliError, Environment> |
| 1178 | ): <Kind extends ParamKind>(self: Param<Kind, A>) => Param<Kind, B> |
| 1179 | <Kind extends ParamKind, A, B>( |
| 1180 | self: Param<Kind, A>, |
| 1181 | f: (a: A) => Effect.Effect<B, CliError.CliError, Environment> |
| 1182 | ): Param<Kind, B> |
| 1183 | } = dual(2, <Kind extends ParamKind, A, B>( |
| 1184 | self: Param<Kind, A>, |
| 1185 | f: (a: A) => Effect.Effect<B, CliError.CliError, Environment> |
| 1186 | ) => |
| 1187 | transform( |
| 1188 | self, |
| 1189 | (parse: Parse<A>) => (args: ParsedArgs) => |
| 1190 | Effect.flatMap(parse(args), ([leftover, a]) => |
| 1191 | f(a).pipe( |
| 1192 | Effect.map((b) => [leftover, b] as const) |
| 1193 | )) |
| 1194 | )) |
| 1195 | |
| 1196 | /** |
| 1197 | * Transforms the parsed value of an option using a function that may throw, |
| 1198 | * converting any thrown errors into failure messages. |
| 1199 | * |
| 1200 | * **Example** (Mapping thrown errors) |
| 1201 | * |
| 1202 | * ```ts import.meta.vitest |
| 1203 | * import { Effect, FileSystem, Layer, Path, Stdio, Terminal } from "effect" |
| 1204 | * import { Param } from "effect/cli" |
| 1205 | * import { ChildProcessSpawner } from "effect/process" |
| 1206 | * |
| 1207 | * const CliTestLayer = Layer.mergeAll( |
| 1208 | * FileSystem.layerNoop({}), |
| 1209 | * Path.layer, |
| 1210 | * Stdio.layerTest({}), |
| 1211 | * Layer.succeed(Terminal.Terminal, Terminal.make({ |
| 1212 | * columns: Effect.succeed(80), |
| 1213 | * rows: Effect.succeed(24), |
| 1214 | * readInput: Effect.die("unused"), |
| 1215 | * readLine: Effect.die("unused"), |
| 1216 | * display: () => Effect.void |
| 1217 | * })), |
| 1218 | * Layer.succeed( |
| 1219 | * ChildProcessSpawner.ChildProcessSpawner, |
| 1220 | * ChildProcessSpawner.make(() => Effect.die("unused")) |
| 1221 | * ) |
| 1222 | * ) |
| 1223 | * |
| 1224 | * const parsedJson = Param.String(Param.flagKind, "config").pipe( |
| 1225 | * Param.mapTryCatch( |
| 1226 | * (str) => JSON.parse(str), |
| 1227 | * (error) => |
| 1228 | * `Invalid JSON: ${error instanceof Error ? error.message : String(error)}` |
| 1229 | * ) |
| 1230 | * ) |
| 1231 | * |
| 1232 | * const [, value] = await Effect.runPromise( |
| 1233 | * parsedJson.parse({ |
| 1234 | * arguments: [], |
| 1235 | * flags: { config: ['{"enabled":true}'] } |
| 1236 | * }).pipe(Effect.provide(CliTestLayer)) |
| 1237 | * ) |
| 1238 | * value // => { enabled: true } |
| 1239 | * ``` |
| 1240 | * |
| 1241 | * @stability unstable |
| 1242 | * @category combinators |
| 1243 | * @since 4.0.0 |
| 1244 | */ |
| 1245 | export const mapTryCatch: { |
| 1246 | <A, B>( |
| 1247 | f: (a: A) => B, |
| 1248 | onError: (error: unknown) => string |
| 1249 | ): <Kind extends ParamKind>(self: Param<Kind, A>) => Param<Kind, B> |
| 1250 | <Kind extends ParamKind, A, B>( |
| 1251 | self: Param<Kind, A>, |
| 1252 | f: (a: A) => B, |
| 1253 | onError: (error: unknown) => string |
| 1254 | ): Param<Kind, B> |
| 1255 | } = dual(3, <Kind extends ParamKind, A, B>( |
| 1256 | self: Param<Kind, A>, |
| 1257 | f: (a: A) => B, |
| 1258 | onError: (error: unknown) => string |
| 1259 | ) => { |
| 1260 | const single = getUnderlyingSingleOrThrow(self) |
| 1261 | |
| 1262 | return transform( |
| 1263 | self, |
| 1264 | (parse: Parse<A>) => (args: ParsedArgs) => |
| 1265 | Effect.flatMap(parse(args), ([leftover, a]) => |
| 1266 | Effect.try({ |
| 1267 | try: () => f(a), |
| 1268 | catch: (error) => onError(error) |
| 1269 | }).pipe( |
| 1270 | Effect.mapError( |
| 1271 | (error) => |
| 1272 | new CliError.InvalidValue({ |
| 1273 | option: single.name, |
| 1274 | value: globalThis.String(a), |
| 1275 | expected: error, |
| 1276 | kind: single.kind |
| 1277 | }) |
| 1278 | ), |
| 1279 | Effect.map((b) => [leftover, b] as const) |
| 1280 | )) |
| 1281 | ) |
| 1282 | }) |
| 1283 | |
| 1284 | /** |
| 1285 | * Makes a flag or positional argument optional. |
| 1286 | * |
| 1287 | * **Details** |
| 1288 | * |
| 1289 | * When the parameter is absent, parsing succeeds with `Option.none()` instead |
| 1290 | * of failing with a missing option or missing argument error. When present, the |
| 1291 | * parsed value is wrapped in `Option.some()`. |
| 1292 | * |
| 1293 | * **Example** (Making parameters optional) |
| 1294 | * |
| 1295 | * ```ts import.meta.vitest |
| 1296 | * import { Param } from "effect/cli" |
| 1297 | * |
| 1298 | * // Create an optional port option |
| 1299 | * // - When not provided: returns Option.none() |
| 1300 | * // - When provided: returns Option.some(parsedValue) |
| 1301 | * const port = Param.optional(Param.Int(Param.flagKind, "port")) |
| 1302 | * port.kind // => "flag" |
| 1303 | * ``` |
| 1304 | * |
| 1305 | * @stability unstable |
| 1306 | * @category combinators |
| 1307 | * @since 4.0.0 |
| 1308 | */ |
| 1309 | export const optional = <Kind extends ParamKind, A>( |
| 1310 | param: Param<Kind, A> |
| 1311 | ): Param<Kind, Option.Option<A>> => { |
| 1312 | const parse: Parse<Option.Option<A>> = (args) => |
| 1313 | param.parse(args).pipe( |
| 1314 | Effect.map(([leftover, value]) => [leftover, Option.some(value)] as const), |
| 1315 | // Catch both MissingOption (for flags) and MissingArgument (for positional arguments) |
| 1316 | Effect.catchTags({ |
| 1317 | MissingOption: () => Effect.succeed([args.arguments, Option.none()] as const), |
| 1318 | MissingArgument: () => Effect.succeed([args.arguments, Option.none()] as const) |
| 1319 | }) |
| 1320 | ) |
| 1321 | return Object.assign(Object.create(Proto), { |
| 1322 | _tag: "Optional", |
| 1323 | kind: param.kind, |
| 1324 | param, |
| 1325 | parse |
| 1326 | }) |
| 1327 | } |
| 1328 | |
| 1329 | /** |
| 1330 | * Makes a flag or positional argument optional by supplying a fallback value. |
| 1331 | * |
| 1332 | * **Details** |
| 1333 | * |
| 1334 | * The fallback may be a pure value or an effect. It is used only when the |
| 1335 | * parameter is absent; provided values are parsed normally. |
| 1336 | * |
| 1337 | * **Example** (Providing default values) |
| 1338 | * |
| 1339 | * ```ts import.meta.vitest |
| 1340 | * import { Param } from "effect/cli" |
| 1341 | * |
| 1342 | * // Using the pipe operator to make an option optional |
| 1343 | * const port = Param.Int(Param.flagKind, "port").pipe( |
| 1344 | * Param.withDefault(8080) |
| 1345 | * ) |
| 1346 | * |
| 1347 | * // Can also be used with other combinators |
| 1348 | * const verbose = Param.Boolean(Param.flagKind, "verbose").pipe( |
| 1349 | * Param.withAlias("-v"), |
| 1350 | * Param.withDescription("Enable verbose output"), |
| 1351 | * Param.withDefault(false) |
| 1352 | * ) |
| 1353 | * const kinds = [port.kind, verbose.kind] // => ["flag", "flag"] |
| 1354 | * ``` |
| 1355 | * |
| 1356 | * @stability unstable |
| 1357 | * @category combinators |
| 1358 | * @since 4.0.0 |
| 1359 | */ |
| 1360 | export const withDefault: { |
| 1361 | <const B>( |
| 1362 | defaultValue: B | Effect.Effect<B, CliError.CliError, Environment> |
| 1363 | ): <Kind extends ParamKind, A>(self: Param<Kind, A>) => Param<Kind, A | B> |
| 1364 | <Kind extends ParamKind, A, const B>( |
| 1365 | self: Param<Kind, A>, |
| 1366 | defaultValue: B | Effect.Effect<B, CliError.CliError, Environment> |
| 1367 | ): Param<Kind, A | B> |
| 1368 | } = dual(2, <Kind extends ParamKind, A, const B>( |
| 1369 | self: Param<Kind, A>, |
| 1370 | defaultValue: B | Effect.Effect<B, CliError.CliError, Environment> |
| 1371 | ): Param<Kind, A | B> => { |
| 1372 | if (!Effect.isEffect(defaultValue)) { |
| 1373 | return map(optional(self), Option.getOrElse(() => defaultValue)) |
| 1374 | } |
| 1375 | return mapEffect( |
| 1376 | optional(self as Param<Kind, A | B>), |
| 1377 | Option.match({ |
| 1378 | onNone: () => defaultValue, |
| 1379 | onSome: Effect.succeed |
| 1380 | }) |
| 1381 | ) |
| 1382 | }) |
| 1383 | |
| 1384 | /** |
| 1385 | * Adds a fallback config that is loaded when a required parameter is missing. |
| 1386 | * |
| 1387 | * **When to use** |
| 1388 | * |
| 1389 | * Use when you need config to provide a fallback source for required flags or |
| 1390 | * arguments that are absent from CLI input. |
| 1391 | * |
| 1392 | * **Details** |
| 1393 | * |
| 1394 | * Provided CLI values win. Config is loaded only after a missing option or |
| 1395 | * missing argument error. |
| 1396 | * |
| 1397 | * **Gotchas** |
| 1398 | * |
| 1399 | * Missing config preserves the original missing-parameter error. Config parse |
| 1400 | * failure becomes `CliError.InvalidValue`. |
| 1401 | * |
| 1402 | * @see {@link withDefault} for a pure default value |
| 1403 | * @see {@link withFallbackPrompt} for prompting interactively when input is missing |
| 1404 | * |
| 1405 | * @stability unstable |
| 1406 | * @category combinators |
| 1407 | * @since 4.0.0 |
| 1408 | */ |
| 1409 | export const withFallbackConfig: { |
| 1410 | <B>(config: Config.Config<B>): <Kind extends ParamKind, A>(self: Param<Kind, A>) => Param<Kind, A | B> |
| 1411 | <Kind extends ParamKind, A, B>(self: Param<Kind, A>, config: Config.Config<B>): Param<Kind, A | B> |
| 1412 | } = dual(2, <Kind extends ParamKind, A, B>( |
| 1413 | self: Param<Kind, A>, |
| 1414 | config: Config.Config<B> |
| 1415 | ): Param<Kind, A | B> => { |
| 1416 | const toInvalidValue = ( |
| 1417 | error: CliError.MissingOption | CliError.MissingArgument, |
| 1418 | configError: Config.ConfigError |
| 1419 | ): CliError.InvalidValue => |
| 1420 | new CliError.InvalidValue({ |
| 1421 | option: error._tag === "MissingOption" ? error.option : error.argument, |
| 1422 | value: "config", |
| 1423 | expected: configError.message, |
| 1424 | kind: error._tag === "MissingOption" ? "flag" : "argument" |
| 1425 | }) |
| 1426 | const runConfig = (error: CliError.MissingOption | CliError.MissingArgument, args: ParsedArgs) => |
| 1427 | Config.option(config).pipe( |
| 1428 | Effect.mapError((configError) => toInvalidValue(error, configError)), |
| 1429 | Effect.flatMap(Option.match({ |
| 1430 | onNone: () => Effect.fail(error), |
| 1431 | onSome: (value) => Effect.succeed([args.arguments, value as A | B] as const) |
| 1432 | })) |
| 1433 | ) |
| 1434 | return transform( |
| 1435 | self, |
| 1436 | (parse) => (args) => |
| 1437 | parse(args).pipe( |
| 1438 | Effect.catchTag(["MissingOption", "MissingArgument"], (error) => runConfig(error, args)) |
| 1439 | ) |
| 1440 | ) |
| 1441 | }) |
| 1442 | |
| 1443 | /** |
| 1444 | * Adds a fallback prompt that is shown when a required parameter is missing. |
| 1445 | * |
| 1446 | * **When to use** |
| 1447 | * |
| 1448 | * Use when a CLI should ask interactively for a missing required flag or |
| 1449 | * argument. |
| 1450 | * |
| 1451 | * **Details** |
| 1452 | * |
| 1453 | * `FallbackPrompt` accepts either a `Prompt` or an effect that builds one. |
| 1454 | * Effectful prompt creation is lazy and runs only when the fallback is needed. |
| 1455 | * |
| 1456 | * **Gotchas** |
| 1457 | * |
| 1458 | * This only handles missing options and missing arguments. Invalid values do not |
| 1459 | * prompt, and prompt cancellation re-fails with the original missing error. |
| 1460 | * |
| 1461 | * @see {@link FallbackPrompt} for accepted fallback prompt forms |
| 1462 | * @see {@link withFallbackConfig} for loading a fallback from config |
| 1463 | * @see {@link withDefault} for a pure default value |
| 1464 | * |
| 1465 | * @stability unstable |
| 1466 | * @category combinators |
| 1467 | * @since 4.0.0 |
| 1468 | */ |
| 1469 | export const withFallbackPrompt: { |
| 1470 | <B>(prompt: FallbackPrompt<B>): <Kind extends ParamKind, A>(self: Param<Kind, A>) => Param<Kind, A | B> |
| 1471 | <Kind extends ParamKind, A, B>(self: Param<Kind, A>, prompt: FallbackPrompt<B>): Param<Kind, A | B> |
| 1472 | } = dual(2, <Kind extends ParamKind, A, B>( |
| 1473 | self: Param<Kind, A>, |
| 1474 | prompt: FallbackPrompt<B> |
| 1475 | ): Param<Kind, A | B> => { |
| 1476 | const runPrompt = (error: CliError.MissingOption | CliError.MissingArgument, args: ParsedArgs) => |
| 1477 | Effect.flatMap(Prompt.isPrompt(prompt) ? Effect.succeed(prompt) : prompt, Prompt.run).pipe( |
| 1478 | Effect.map((value) => [args.arguments, value as A | B] as const), |
| 1479 | Effect.catchTag("QuitError", () => Effect.fail(error)) |
| 1480 | ) |
| 1481 | return transform( |
| 1482 | self, |
| 1483 | (parse) => (args) => |
| 1484 | parse(args).pipe( |
| 1485 | Effect.catchTag(["MissingOption", "MissingArgument"], (error) => runPrompt(error, args)) |
| 1486 | ) |
| 1487 | ) |
| 1488 | }) |
| 1489 | |
| 1490 | /** |
| 1491 | * Represent options which can be used to configure variadic parameters. |
| 1492 | * |
| 1493 | * @stability unstable |
| 1494 | * @category options |
| 1495 | * @since 4.0.0 |
| 1496 | */ |
| 1497 | export type VariadicParamOptions = { |
| 1498 | /** |
| 1499 | * The minimum number of times the parameter can be specified. |
| 1500 | */ |
| 1501 | readonly min?: number | undefined |
| 1502 | /** |
| 1503 | * The maximum number of times the parameter can be specified. |
| 1504 | */ |
| 1505 | readonly max?: number | undefined |
| 1506 | } |
| 1507 | |
| 1508 | /** |
| 1509 | * Creates a variadic parameter that can be specified multiple times. |
| 1510 | * |
| 1511 | * **Details** |
| 1512 | * |
| 1513 | * This is the base combinator for creating parameters that accept multiple values. |
| 1514 | * The `min` and `max` parameters are optional. When they are not provided, the |
| 1515 | * parameter can be specified any number of times, from 0 to infinity. |
| 1516 | * |
| 1517 | * **Example** (Accepting multiple values) |
| 1518 | * |
| 1519 | * ```ts import.meta.vitest |
| 1520 | * import { Param } from "effect/cli" |
| 1521 | * |
| 1522 | * // Basic variadic parameter (0 to infinity) |
| 1523 | * const tags = Param.variadic(Param.String(Param.flagKind, "tag")) |
| 1524 | * |
| 1525 | * // Variadic with minimum count |
| 1526 | * const inputs = Param.variadic( |
| 1527 | * Param.String(Param.flagKind, "input"), |
| 1528 | * { min: 1 } // at least 1 required |
| 1529 | * ) |
| 1530 | * |
| 1531 | * // Variadic with both min and max |
| 1532 | * const limited = Param.variadic(Param.String(Param.flagKind, "item"), { |
| 1533 | * min: 2, // at least 2 times |
| 1534 | * max: 2 // at most 2 times |
| 1535 | * }) |
| 1536 | * const kinds = [tags.kind, inputs.kind, limited.kind] // => ["flag", "flag", "flag"] |
| 1537 | * ``` |
| 1538 | * |
| 1539 | * @stability unstable |
| 1540 | * @category combinators |
| 1541 | * @since 4.0.0 |
| 1542 | */ |
| 1543 | export const variadic = <Kind extends ParamKind, A>( |
| 1544 | self: Param<Kind, A>, |
| 1545 | options?: VariadicParamOptions | undefined |
| 1546 | ): Param<Kind, ReadonlyArray<A>> => { |
| 1547 | const single = getUnderlyingSingleOrThrow(self) |
| 1548 | const parse: Parse<ReadonlyArray<A>> = (args) => { |
| 1549 | if (single.kind === "argument") { |
| 1550 | return parsePositionalVariadic(self, single, args, options) |
| 1551 | } else { |
| 1552 | return parseOptionVariadic(self, single, args, options) |
| 1553 | } |
| 1554 | } |
| 1555 | return Object.assign(Object.create(Proto), { |
| 1556 | _tag: "Variadic", |
| 1557 | kind: self.kind, |
| 1558 | param: self, |
| 1559 | min: Option.fromUndefinedOr(options?.min), |
| 1560 | max: Option.fromUndefinedOr(options?.max), |
| 1561 | parse |
| 1562 | }) |
| 1563 | } |
| 1564 | |
| 1565 | /** |
| 1566 | * Wraps an option to allow it to be specified multiple times within a range. |
| 1567 | * |
| 1568 | * **Details** |
| 1569 | * |
| 1570 | * This combinator transforms an option to accept between `min` and `max` |
| 1571 | * occurrences on the command line, returning an array of all provided values. |
| 1572 | * |
| 1573 | * **Example** (Bounding repeated values) |
| 1574 | * |
| 1575 | * ```ts import.meta.vitest |
| 1576 | * import { Param } from "effect/cli" |
| 1577 | * |
| 1578 | * // Allow 1-3 file inputs |
| 1579 | * const files = Param.String(Param.flagKind, "file").pipe( |
| 1580 | * Param.between(1, 3), |
| 1581 | * Param.withAlias("-f") |
| 1582 | * ) |
| 1583 | * |
| 1584 | * // Parse: --file a.txt --file b.txt |
| 1585 | * // Result: ["a.txt", "b.txt"] |
| 1586 | * |
| 1587 | * // Allow 0 or more tags |
| 1588 | * const tags = Param.String(Param.flagKind, "tag").pipe( |
| 1589 | * Param.between(0, Number.MAX_SAFE_INTEGER) |
| 1590 | * ) |
| 1591 | * |
| 1592 | * // Parse: --tag dev --tag staging --tag v1.0 |
| 1593 | * // Result: ["dev", "staging", "v1.0"] |
| 1594 | * const kinds = [files.kind, tags.kind] // => ["flag", "flag"] |
| 1595 | * ``` |
| 1596 | * |
| 1597 | * @stability unstable |
| 1598 | * @category combinators |
| 1599 | * @since 4.0.0 |
| 1600 | */ |
| 1601 | export const between: { |
| 1602 | <A>(min: number, max: number): <Kind extends ParamKind>(self: Param<Kind, A>) => Param<Kind, ReadonlyArray<A>> |
| 1603 | <Kind extends ParamKind, A>(self: Param<Kind, A>, min: number, max: number): Param<Kind, ReadonlyArray<A>> |
| 1604 | } = dual(3, <Kind extends ParamKind, A>(self: Param<Kind, A>, min: number, max: number) => { |
| 1605 | if (min < 0) { |
| 1606 | throw new Error("between: min must be non-negative") |
| 1607 | } |
| 1608 | if (max < min) { |
| 1609 | throw new Error("between: max must be greater than or equal to min") |
| 1610 | } |
| 1611 | |
| 1612 | return variadic(self, { min, max }) |
| 1613 | }) |
| 1614 | |
| 1615 | /** |
| 1616 | * Wraps an option to allow it to be specified at most `max` times. |
| 1617 | * |
| 1618 | * **Details** |
| 1619 | * |
| 1620 | * This combinator transforms an option to accept between 0 and `max` |
| 1621 | * occurrences on the command line, returning an array of all provided values. |
| 1622 | * |
| 1623 | * **Example** (Limiting repeated values) |
| 1624 | * |
| 1625 | * ```ts import.meta.vitest |
| 1626 | * import { Param } from "effect/cli" |
| 1627 | * |
| 1628 | * // Allow at most 3 warning suppressions |
| 1629 | * const suppressions = Param.String(Param.flagKind, "suppress").pipe( |
| 1630 | * Param.atMost(3) |
| 1631 | * ) |
| 1632 | * |
| 1633 | * // Parse: --suppress warning1 --suppress warning2 |
| 1634 | * // Result: ["warning1", "warning2"] |
| 1635 | * suppressions.kind // => "flag" |
| 1636 | * ``` |
| 1637 | * |
| 1638 | * @stability unstable |
| 1639 | * @category combinators |
| 1640 | * @since 4.0.0 |
| 1641 | */ |
| 1642 | export const atMost: { |
| 1643 | <A>(max: number): <Kind extends ParamKind>(self: Param<Kind, A>) => Param<Kind, ReadonlyArray<A>> |
| 1644 | <Kind extends ParamKind, A>(self: Param<Kind, A>, max: number): Param<Kind, ReadonlyArray<A>> |
| 1645 | } = dual(2, <Kind extends ParamKind, A>(self: Param<Kind, A>, max: number) => { |
| 1646 | if (max < 0) { |
| 1647 | throw new Error("atMost: max must be non-negative") |
| 1648 | } |
| 1649 | return variadic(self, { max }) |
| 1650 | }) |
| 1651 | |
| 1652 | /** |
| 1653 | * Wraps an option to require it to be specified at least `min` times. |
| 1654 | * |
| 1655 | * **Details** |
| 1656 | * |
| 1657 | * This combinator transforms an option to accept at least `min` |
| 1658 | * occurrences on the command line, returning an array of all provided values. |
| 1659 | * |
| 1660 | * **Example** (Requiring repeated values) |
| 1661 | * |
| 1662 | * ```ts import.meta.vitest |
| 1663 | * import { Param } from "effect/cli" |
| 1664 | * |
| 1665 | * // Require at least 2 input files |
| 1666 | * const inputs = Param.String(Param.flagKind, "input").pipe( |
| 1667 | * Param.atLeast(2), |
| 1668 | * Param.withAlias("-i") |
| 1669 | * ) |
| 1670 | * |
| 1671 | * // Parse: --input file1.txt --input file2.txt --input file3.txt |
| 1672 | * // Result: ["file1.txt", "file2.txt", "file3.txt"] |
| 1673 | * inputs.kind // => "flag" |
| 1674 | * ``` |
| 1675 | * |
| 1676 | * @stability unstable |
| 1677 | * @category combinators |
| 1678 | * @since 4.0.0 |
| 1679 | */ |
| 1680 | export const atLeast: { |
| 1681 | <A>(min: number): <Kind extends ParamKind>(self: Param<Kind, A>) => Param<Kind, ReadonlyArray<A>> |
| 1682 | <Kind extends ParamKind, A>(self: Param<Kind, A>, min: number): Param<Kind, ReadonlyArray<A>> |
| 1683 | } = dual(2, <Kind extends ParamKind, A>(self: Param<Kind, A>, min: number) => { |
| 1684 | if (min < 0) { |
| 1685 | throw new Error("atLeast: min must be non-negative") |
| 1686 | } |
| 1687 | return variadic(self, { min }) |
| 1688 | }) |
| 1689 | |
| 1690 | /** |
| 1691 | * Filters and transforms parsed values, failing with a custom error message |
| 1692 | * if the filter function returns `Option.none()`. |
| 1693 | * |
| 1694 | * **When to use** |
| 1695 | * |
| 1696 | * Use when you need validation and transformation in a single parameter |
| 1697 | * combinator. |
| 1698 | * |
| 1699 | * **Example** (Filtering and transforming values) |
| 1700 | * |
| 1701 | * ```ts import.meta.vitest |
| 1702 | * import { Option } from "effect" |
| 1703 | * import { Param } from "effect/cli" |
| 1704 | * const positiveInt = Param.Int(Param.flagKind, "count").pipe( |
| 1705 | * Param.filterMap( |
| 1706 | * (n) => n > 0 ? Option.some(n) : Option.none(), |
| 1707 | * (n) => `Expected positive integer, got ${n}` |
| 1708 | * ) |
| 1709 | * ) |
| 1710 | * positiveInt.kind // => "flag" |
| 1711 | * ``` |
| 1712 | * |
| 1713 | * @stability unstable |
| 1714 | * @category combinators |
| 1715 | * @since 4.0.0 |
| 1716 | */ |
| 1717 | export const filterMap: { |
| 1718 | <A, B>( |
| 1719 | filter: (a: A) => Option.Option<B>, |
| 1720 | onNone: (a: A) => string |
| 1721 | ): <Kind extends ParamKind>(self: Param<Kind, A>) => Param<Kind, B> |
| 1722 | <Kind extends ParamKind, A, B>( |
| 1723 | self: Param<Kind, A>, |
| 1724 | filter: (a: A) => Option.Option<B>, |
| 1725 | onNone: (a: A) => string |
| 1726 | ): Param<Kind, B> |
| 1727 | } = dual(3, <Kind extends ParamKind, A, B>( |
| 1728 | self: Param<Kind, A>, |
| 1729 | filter: (a: A) => Option.Option<B>, |
| 1730 | onNone: (a: A) => string |
| 1731 | ) => |
| 1732 | mapEffect( |
| 1733 | self, |
| 1734 | Effect.fnUntraced(function*(a) { |
| 1735 | const result = filter(a) |
| 1736 | if (Option.isSome(result)) { |
| 1737 | return result.value |
| 1738 | } |
| 1739 | const single = getUnderlyingSingleOrThrow(self) |
| 1740 | return yield* new CliError.InvalidValue({ |
| 1741 | option: single.name, |
| 1742 | value: globalThis.String(a), |
| 1743 | expected: onNone(a), |
| 1744 | kind: single.kind |
| 1745 | }) |
| 1746 | }) |
| 1747 | )) |
| 1748 | |
| 1749 | /** |
| 1750 | * Filters parsed values, failing with a custom error message if the predicate returns false. |
| 1751 | * |
| 1752 | * **Example** (Filtering parsed values) |
| 1753 | * |
| 1754 | * ```ts import.meta.vitest |
| 1755 | * import { Param } from "effect/cli" |
| 1756 | * |
| 1757 | * const evenNumber = Param.Int(Param.flagKind, "num").pipe( |
| 1758 | * Param.filter( |
| 1759 | * (n) => n % 2 === 0, |
| 1760 | * (n) => `Expected even number, got ${n}` |
| 1761 | * ) |
| 1762 | * ) |
| 1763 | * evenNumber.kind // => "flag" |
| 1764 | * ``` |
| 1765 | * |
| 1766 | * @stability unstable |
| 1767 | * @category combinators |
| 1768 | * @since 4.0.0 |
| 1769 | */ |
| 1770 | export const filter: { |
| 1771 | <A>( |
| 1772 | predicate: Predicate.Predicate<A>, |
| 1773 | onFalse: (a: A) => string |
| 1774 | ): <Kind extends ParamKind>(self: Param<Kind, A>) => Param<Kind, A> |
| 1775 | <Kind extends ParamKind, A>( |
| 1776 | self: Param<Kind, A>, |
| 1777 | predicate: Predicate.Predicate<A>, |
| 1778 | onFalse: (a: A) => string |
| 1779 | ): Param<Kind, A> |
| 1780 | } = dual(3, <Kind extends ParamKind, A>( |
| 1781 | self: Param<Kind, A>, |
| 1782 | predicate: Predicate.Predicate<A>, |
| 1783 | onFalse: (a: A) => string |
| 1784 | ) => filterMap(self, Option.liftPredicate(predicate), onFalse)) |
| 1785 | |
| 1786 | /** |
| 1787 | * Sets a custom metavar (placeholder name) for the param in help documentation. |
| 1788 | * |
| 1789 | * **Details** |
| 1790 | * |
| 1791 | * The metavar is displayed in usage text to indicate what value the user should provide. |
| 1792 | * For example, `--output FILE` shows `FILE` as the metavar. |
| 1793 | * |
| 1794 | * **Example** (Setting metavars) |
| 1795 | * |
| 1796 | * ```ts import.meta.vitest |
| 1797 | * import { Param } from "effect/cli" |
| 1798 | * |
| 1799 | * const port = Param.Int(Param.flagKind, "port").pipe( |
| 1800 | * Param.withMetavar("PORT"), |
| 1801 | * Param.filter( |
| 1802 | * (p) => p >= 1 && p <= 65535, |
| 1803 | * () => "Port must be between 1 and 65535" |
| 1804 | * ) |
| 1805 | * ) |
| 1806 | * port.kind // => "flag" |
| 1807 | * ``` |
| 1808 | * |
| 1809 | * @stability unstable |
| 1810 | * @category metadata |
| 1811 | * @since 4.0.0 |
| 1812 | */ |
| 1813 | export const withMetavar: { |
| 1814 | <K extends ParamKind>(metavar: string): <A>(self: Param<K, A>) => Param<K, A> |
| 1815 | <K extends ParamKind, A>(self: Param<K, A>, metavar: string): Param<K, A> |
| 1816 | } = dual(2, <K extends ParamKind, A>( |
| 1817 | self: Param<K, A>, |
| 1818 | metavar: string |
| 1819 | ) => |
| 1820 | transformSingle(self, (single) => |
| 1821 | makeSingle({ |
| 1822 | ...single, |
| 1823 | typeName: metavar |
| 1824 | }))) |
| 1825 | |
| 1826 | /** |
| 1827 | * Validates parsed values against a Schema, providing detailed error messages. |
| 1828 | * |
| 1829 | * **Example** (Validating with schemas) |
| 1830 | * |
| 1831 | * ```ts import.meta.vitest |
| 1832 | * import { Schema } from "effect" |
| 1833 | * import { Param } from "effect/cli" |
| 1834 | * const isEmail = Schema.isPattern(/^[^\s@]+@[^\s@]+\.[^\s@]+$/) |
| 1835 | * |
| 1836 | * const Email = Schema.String.pipe( |
| 1837 | * Schema.check(isEmail) |
| 1838 | * ) |
| 1839 | * |
| 1840 | * const email = Param.String(Param.flagKind, "email").pipe( |
| 1841 | * Param.withSchema(Email) |
| 1842 | * ) |
| 1843 | * email.kind // => "flag" |
| 1844 | * ``` |
| 1845 | * |
| 1846 | * @stability unstable |
| 1847 | * @category combinators |
| 1848 | * @since 4.0.0 |
| 1849 | */ |
| 1850 | export const withSchema: { |
| 1851 | <A, B>( |
| 1852 | schema: Schema.ConstraintCodec<B, A, Environment, unknown> |
| 1853 | ): <Kind extends ParamKind>( |
| 1854 | self: Param<Kind, A> |
| 1855 | ) => Param<Kind, B> |
| 1856 | <Kind extends ParamKind, A, B>( |
| 1857 | self: Param<Kind, A>, |
| 1858 | schema: Schema.ConstraintCodec<B, A, Environment, unknown> |
| 1859 | ): Param<Kind, B> |
| 1860 | } = dual(2, <Kind extends ParamKind, A, B>( |
| 1861 | self: Param<Kind, A>, |
| 1862 | schema: Schema.ConstraintCodec<B, A, Environment, unknown> |
| 1863 | ) => { |
| 1864 | const decodeParam = Schema.decodeUnknownEffect(schema) |
| 1865 | return mapEffect(self, (value) => |
| 1866 | Effect.mapError(decodeParam(value), (error) => { |
| 1867 | const single = getUnderlyingSingleOrThrow(self) |
| 1868 | return new CliError.InvalidValue({ |
| 1869 | option: single.name, |
| 1870 | value: globalThis.String(value), |
| 1871 | expected: `Schema validation failed: ${error.message}`, |
| 1872 | kind: single.kind |
| 1873 | }) |
| 1874 | })) |
| 1875 | }) |
| 1876 | |
| 1877 | /** |
| 1878 | * Provides a fallback param to use if this param fails to parse. |
| 1879 | * |
| 1880 | * **Example** (Falling back to another parameter) |
| 1881 | * |
| 1882 | * ```ts import.meta.vitest |
| 1883 | * import { Param } from "effect/cli" |
| 1884 | * |
| 1885 | * const config = Param.File(Param.flagKind, "config").pipe( |
| 1886 | * Param.orElse(() => Param.String(Param.flagKind, "config-url")) |
| 1887 | * ) |
| 1888 | * config.kind // => "flag" |
| 1889 | * ``` |
| 1890 | * |
| 1891 | * @stability unstable |
| 1892 | * @category combinators |
| 1893 | * @since 4.0.0 |
| 1894 | */ |
| 1895 | export const orElse: { |
| 1896 | <B, Kind extends ParamKind>(orElse: LazyArg<Param<Kind, B>>): <A>(self: Param<Kind, A>) => Param<Kind, A | B> |
| 1897 | <Kind extends ParamKind, A, B>( |
| 1898 | self: Param<Kind, A>, |
| 1899 | orElse: LazyArg<Param<Kind, B>> |
| 1900 | ): Param<Kind, A | B> |
| 1901 | } = dual(2, <Kind extends ParamKind, A, B>( |
| 1902 | self: Param<Kind, A>, |
| 1903 | orElse: LazyArg<Param<Kind, B>> |
| 1904 | ) => |
| 1905 | transform( |
| 1906 | self, |
| 1907 | (parse: Parse<A>, alternatives): Parse<A | B> => (args: ParsedArgs) => |
| 1908 | Effect.catch(parse(args), () => (alternatives[0]!() as Parse<B>)(args)), |
| 1909 | [orElse] |
| 1910 | )) |
| 1911 | |
| 1912 | /** |
| 1913 | * Provides a fallback param and returns a `Result` indicating which param |
| 1914 | * succeeded. |
| 1915 | * |
| 1916 | * **Details** |
| 1917 | * |
| 1918 | * The original param's value is returned as `Result.succeed`, while the |
| 1919 | * fallback param's value is returned as `Result.fail`. |
| 1920 | * |
| 1921 | * **Example** (Returning fallback results) |
| 1922 | * |
| 1923 | * ```ts import.meta.vitest |
| 1924 | * import { Param } from "effect/cli" |
| 1925 | * |
| 1926 | * const configSource = Param.File(Param.flagKind, "config").pipe( |
| 1927 | * Param.orElseResult(() => Param.String(Param.flagKind, "config-url")) |
| 1928 | * ) |
| 1929 | * // Returns Result<string, string> |
| 1930 | * configSource.kind // => "flag" |
| 1931 | * ``` |
| 1932 | * |
| 1933 | * @stability unstable |
| 1934 | * @category combinators |
| 1935 | * @since 4.0.0 |
| 1936 | */ |
| 1937 | export const orElseResult: { |
| 1938 | <Kind extends ParamKind, B>( |
| 1939 | orElse: LazyArg<Param<Kind, B>> |
| 1940 | ): <A>(self: Param<Kind, A>) => Param<Kind, Result.Result<A, B>> |
| 1941 | <Kind extends ParamKind, A, B>( |
| 1942 | self: Param<Kind, A>, |
| 1943 | orElse: LazyArg<Param<Kind, B>> |
| 1944 | ): Param<Kind, Result.Result<A, B>> |
| 1945 | } = dual(2, <Kind extends ParamKind, A, B>( |
| 1946 | self: Param<Kind, A>, |
| 1947 | orElse: LazyArg<Param<Kind, B>> |
| 1948 | ) => { |
| 1949 | return transform( |
| 1950 | self, |
| 1951 | (parse: Parse<A>, alternatives): Parse<Result.Result<A, B>> => (args: ParsedArgs) => |
| 1952 | Effect.catch( |
| 1953 | Effect.map(parse(args), ([leftover, value]) => [leftover, Result.succeed(value)] as const), |
| 1954 | () => |
| 1955 | Effect.map( |
| 1956 | (alternatives[0]!() as Parse<B>)(args), |
| 1957 | ([leftover, value]) => [leftover, Result.fail(value)] as const |
| 1958 | ) |
| 1959 | ), |
| 1960 | [orElse] |
| 1961 | ) |
| 1962 | }) |
| 1963 | |
| 1964 | // ============================================================================= |
| 1965 | // Parsing Utilities |
| 1966 | // ============================================================================= |
| 1967 | |
| 1968 | const parsePositional: <A>( |
| 1969 | name: string, |
| 1970 | primitiveType: Primitive.Primitive<A>, |
| 1971 | args: ParsedArgs |
| 1972 | ) => Effect.Effect< |
| 1973 | readonly [leftover: ReadonlyArray<string>, value: A], |
| 1974 | CliError.CliError, |
| 1975 | Environment |
| 1976 | > = Effect.fnUntraced(function*(name, primitiveType, args) { |
| 1977 | if (args.arguments.length === 0) { |
| 1978 | return yield* new CliError.MissingArgument({ argument: name }) |
| 1979 | } |
| 1980 | |
| 1981 | const arg = args.arguments[0] |
| 1982 | const value = yield* Effect.mapError( |
| 1983 | primitiveType.parse(arg), |
| 1984 | (error) => |
| 1985 | new CliError.InvalidValue({ |
| 1986 | option: name, |
| 1987 | value: arg, |
| 1988 | expected: error, |
| 1989 | kind: "argument" |
| 1990 | }) |
| 1991 | ) |
| 1992 | |
| 1993 | return [args.arguments.slice(1), value] as const |
| 1994 | }) |
| 1995 | |
| 1996 | const parseFlag: <A>( |
| 1997 | name: string, |
| 1998 | primitiveType: Primitive.Primitive<A>, |
| 1999 | args: ParsedArgs |
| 2000 | ) => Effect.Effect< |
| 2001 | readonly [remainingOperands: ReadonlyArray<string>, value: A], |
| 2002 | CliError.CliError, |
| 2003 | Environment |
| 2004 | > = Effect.fnUntraced(function*(name, primitiveType, args) { |
| 2005 | const providedValues = args.flags[name] |
| 2006 | |
| 2007 | if (providedValues === undefined || providedValues.length === 0) { |
| 2008 | return yield* new CliError.MissingOption({ option: name }) |
| 2009 | } |
| 2010 | |
| 2011 | // Parse the first value (later we can handle multiple) |
| 2012 | const arg = providedValues[0] |
| 2013 | const value = yield* Effect.mapError( |
| 2014 | primitiveType.parse(arg), |
| 2015 | (error) => |
| 2016 | new CliError.InvalidValue({ |
| 2017 | option: name, |
| 2018 | value: arg, |
| 2019 | expected: error, |
| 2020 | kind: "flag" |
| 2021 | }) |
| 2022 | ) |
| 2023 | |
| 2024 | return [args.arguments, value] as const |
| 2025 | }) |
| 2026 | |
| 2027 | const parsePositionalVariadic: <Kind extends ParamKind, A>( |
| 2028 | self: Param<Kind, A>, |
| 2029 | single: Single<Kind, A>, |
| 2030 | args: ParsedArgs, |
| 2031 | options?: VariadicParamOptions | undefined |
| 2032 | ) => Effect.Effect< |
| 2033 | readonly [remainingOperands: ReadonlyArray<string>, value: ReadonlyArray<A>], |
| 2034 | CliError.CliError, |
| 2035 | Environment |
| 2036 | > = Effect.fnUntraced(function*<A, Kind extends ParamKind>( |
| 2037 | self: Param<Kind, A>, |
| 2038 | single: Single<Kind, A>, |
| 2039 | args: ParsedArgs, |
| 2040 | options?: VariadicParamOptions | undefined |
| 2041 | ) { |
| 2042 | const results: Array<A> = [] |
| 2043 | const minValue = options?.min ?? 0 |
| 2044 | const maxValue = options?.max ?? Number.POSITIVE_INFINITY |
| 2045 | |
| 2046 | let count = 0 |
| 2047 | let currentArgs = args.arguments |
| 2048 | while (currentArgs.length > 0 && count < maxValue) { |
| 2049 | const [remainingArgs, value] = yield* self.parse({ |
| 2050 | flags: args.flags, |
| 2051 | arguments: currentArgs |
| 2052 | }) |
| 2053 | results.push(value) |
| 2054 | currentArgs = remainingArgs |
| 2055 | count++ |
| 2056 | } |
| 2057 | |
| 2058 | if (count < minValue) { |
| 2059 | return yield* count === 0 |
| 2060 | ? new CliError.MissingArgument({ argument: single.name }) |
| 2061 | : new CliError.InvalidValue({ |
| 2062 | option: single.name, |
| 2063 | value: `${count} values`, |
| 2064 | expected: `at least ${minValue} value${minValue === 1 ? "" : "s"}`, |
| 2065 | kind: single.kind |
| 2066 | }) |
| 2067 | } |
| 2068 | |
| 2069 | return [currentArgs, results] as const |
| 2070 | }) |
| 2071 | |
| 2072 | const parseOptionVariadic: <Kind extends ParamKind, A>( |
| 2073 | self: Param<Kind, A>, |
| 2074 | single: Single<Kind, A>, |
| 2075 | args: ParsedArgs, |
| 2076 | options?: VariadicParamOptions | undefined |
| 2077 | ) => Effect.Effect< |
| 2078 | readonly [remainingOperands: ReadonlyArray<string>, value: ReadonlyArray<A>], |
| 2079 | CliError.CliError, |
| 2080 | Environment |
| 2081 | > = Effect.fnUntraced(function*<A, Kind extends ParamKind>( |
| 2082 | self: Param<Kind, A>, |
| 2083 | single: Single<Kind, A>, |
| 2084 | args: ParsedArgs, |
| 2085 | options?: VariadicParamOptions | undefined |
| 2086 | ) { |
| 2087 | const results: Array<A> = [] |
| 2088 | const names = [single.name, ...single.aliases] |
| 2089 | const values = names.flatMap((name) => args.flags[name] ?? []) |
| 2090 | const count = values.length |
| 2091 | |
| 2092 | // Validate count constraints |
| 2093 | if (Predicate.isNotUndefined(options?.min) && count < options.min) { |
| 2094 | return yield* count === 0 |
| 2095 | ? new CliError.MissingOption({ option: single.name }) |
| 2096 | : new CliError.InvalidValue({ |
| 2097 | option: single.name, |
| 2098 | value: `${count} occurrences`, |
| 2099 | expected: `at least ${options.min} value${options.min === 1 ? "" : "s"}`, |
| 2100 | kind: single.kind |
| 2101 | }) |
| 2102 | } |
| 2103 | |
| 2104 | if (Predicate.isNotUndefined(options?.max) && count > options.max) { |
| 2105 | return yield* new CliError.InvalidValue({ |
| 2106 | option: single.name, |
| 2107 | value: `${count} occurrences`, |
| 2108 | expected: `at most ${options.max} value${options.max === 1 ? "" : "s"}`, |
| 2109 | kind: single.kind |
| 2110 | }) |
| 2111 | } |
| 2112 | |
| 2113 | // Parse each value individually |
| 2114 | for (const value of values) { |
| 2115 | const [, parsedValue] = yield* self.parse({ |
| 2116 | flags: { [single.name]: [value] }, |
| 2117 | arguments: [] |
| 2118 | }) |
| 2119 | results.push(parsedValue) |
| 2120 | } |
| 2121 | |
| 2122 | return [args.arguments, results] as const |
| 2123 | }) |
| 2124 | |
| 2125 | type AnyParam<Kind extends ParamKind, A> = |
| 2126 | | Single<Kind, A> |
| 2127 | | Map<Kind, any, A> |
| 2128 | | Transform<Kind, any, A> |
| 2129 | | Optional<Kind, A> |
| 2130 | | Variadic<Kind, A> |
| 2131 | |
| 2132 | /** |
| 2133 | * Type-safe param matcher that handles the unsafe casting internally. |
| 2134 | * This provides a clean API for pattern matching on param types while |
| 2135 | * maintaining type safety at the call site. |
| 2136 | */ |
| 2137 | const matchParam = <Kind extends ParamKind, A, R>( |
| 2138 | param: Param<Kind, A>, |
| 2139 | patterns: { |
| 2140 | Single: (single: Single<Kind, A>) => R |
| 2141 | Map: <X>(mapped: Map<Kind, X, A>) => R |
| 2142 | Transform: <X>(mapped: Transform<Kind, X, A>) => R |
| 2143 | Optional: <X>(optional: Optional<Kind, X>) => R |
| 2144 | Variadic: <X>(variadic: Variadic<Kind, X>) => R |
| 2145 | } |
| 2146 | ): R => { |
| 2147 | const p = param as AnyParam<Kind, A> |
| 2148 | switch (p._tag) { |
| 2149 | case "Single": |
| 2150 | return patterns.Single(p) |
| 2151 | case "Map": |
| 2152 | return patterns.Map(p) |
| 2153 | case "Transform": |
| 2154 | return patterns.Transform(p) |
| 2155 | case "Optional": |
| 2156 | return patterns.Optional(p) |
| 2157 | case "Variadic": |
| 2158 | return patterns.Variadic(p) |
| 2159 | } |
| 2160 | } |
| 2161 | |
| 2162 | /** |
| 2163 | * Recursively transforms a param by applying a function to any `Single` nodes. |
| 2164 | * This is used internally by combinators like `withAlias` to traverse the param tree. |
| 2165 | */ |
| 2166 | const transformSingle = <Kind extends ParamKind, A>( |
| 2167 | param: Param<Kind, A>, |
| 2168 | f: <X>(single: Single<Kind, X>) => Single<Kind, X> |
| 2169 | ): Param<Kind, A> => { |
| 2170 | return matchParam(param, { |
| 2171 | Single: (single) => f(single), |
| 2172 | Map: (mapped) => map(transformSingle(mapped.param, f), mapped.f), |
| 2173 | Transform: (mapped) => |
| 2174 | transform( |
| 2175 | transformSingle(mapped.param, f), |
| 2176 | mapped.f, |
| 2177 | mapped.alternatives.map((alternative) => () => transformSingle(alternative(), f)) |
| 2178 | ), |
| 2179 | Optional: (p) => optional(transformSingle(p.param, f)) as Param<Kind, A>, |
| 2180 | Variadic: (p) => |
| 2181 | variadic(transformSingle(p.param, f), { |
| 2182 | min: Option.getOrUndefined(p.min), |
| 2183 | max: Option.getOrUndefined(p.max) |
| 2184 | }) as Param<Kind, A> |
| 2185 | }) |
| 2186 | } |
| 2187 | |
| 2188 | /** |
| 2189 | * Extracts all Single params from a potentially nested param structure. |
| 2190 | * This handles all param combinators including Map, Transform, Optional, and Variadic. |
| 2191 | * |
| 2192 | * @internal |
| 2193 | */ |
| 2194 | export const extractSingleParams = <Kind extends ParamKind, A>( |
| 2195 | param: Param<Kind, A> |
| 2196 | ): Array<Single<Kind, unknown>> => { |
| 2197 | return matchParam(param, { |
| 2198 | Single: (single) => [single as Single<Kind, unknown>], |
| 2199 | Map: (mapped) => extractSingleParams(mapped.param), |
| 2200 | Transform: (mapped) => [ |
| 2201 | ...extractSingleParams(mapped.param), |
| 2202 | ...mapped.alternatives.flatMap((alternative) => extractSingleParams(alternative())) |
| 2203 | ], |
| 2204 | Optional: (optional) => extractSingleParams(optional.param), |
| 2205 | Variadic: (variadic) => extractSingleParams(variadic.param) |
| 2206 | }) |
| 2207 | } |
| 2208 | |
| 2209 | /** |
| 2210 | * Gets the underlying Single param from a potentially nested param structure. |
| 2211 | * Throws an error if there are no singles or multiple singles found. |
| 2212 | * |
| 2213 | * @internal |
| 2214 | */ |
| 2215 | export const getUnderlyingSingleOrThrow = <Kind extends ParamKind, A>( |
| 2216 | param: Param<Kind, A> |
| 2217 | ): Single<Kind, A> => { |
| 2218 | const singles = extractSingleParams(param) |
| 2219 | |
| 2220 | if (singles.length === 0) { |
| 2221 | throw new Error("No Single param found in param structure") |
| 2222 | } |
| 2223 | |
| 2224 | if (singles.length > 1) { |
| 2225 | throw new Error( |
| 2226 | `Multiple Single params found: ${singles.map((s) => s.name).join(", ")}` |
| 2227 | ) |
| 2228 | } |
| 2229 | |
| 2230 | return singles[0] as Single<Kind, A> |
| 2231 | } |
| 2232 | |
| 2233 | /** |
| 2234 | * Gets param metadata by traversing the structure. |
| 2235 | * |
| 2236 | * @internal |
| 2237 | */ |
| 2238 | export const getParamMetadata = <Kind extends ParamKind, A>( |
| 2239 | param: Param<Kind, A> |
| 2240 | ): { |
| 2241 | readonly isOptional: boolean |
| 2242 | readonly isVariadic: boolean |
| 2243 | readonly variadicMin: Option.Option<number> |
| 2244 | readonly variadicMax: Option.Option<number> |
| 2245 | } => { |
| 2246 | return matchParam(param, { |
| 2247 | Single: () => ({ |
| 2248 | isOptional: false, |
| 2249 | isVariadic: false, |
| 2250 | variadicMin: Option.none(), |
| 2251 | variadicMax: Option.none() |
| 2252 | }), |
| 2253 | Map: (mapped) => getParamMetadata(mapped.param), |
| 2254 | Transform: (mapped) => getParamMetadata(mapped.param), |
| 2255 | Optional: (optional) => ({ |
| 2256 | ...getParamMetadata(optional.param), |
| 2257 | isOptional: true |
| 2258 | }), |
| 2259 | Variadic: (variadic) => ({ |
| 2260 | ...getParamMetadata(variadic.param), |
| 2261 | isVariadic: true, |
| 2262 | variadicMin: variadic.min, |
| 2263 | variadicMax: variadic.max |
| 2264 | }) |
| 2265 | }) |
| 2266 | } |