| 1 | /** |
| 2 | * Structured help documentation model for the unstable CLI package. A |
| 3 | * `HelpDoc` value captures the user-facing parts of a command, including its |
| 4 | * description, usage string, positional arguments, flags, global flags, |
| 5 | * subcommands, annotations, and examples. |
| 6 | * |
| 7 | * This module only defines the data shapes used to describe help. Rendering |
| 8 | * that data as terminal text is handled by `CliOutput`. |
| 9 | * |
| 10 | * @stability unstable |
| 11 | * @since 4.0.0 |
| 12 | */ |
| 13 | |
| 14 | import type { NonEmptyReadonlyArray } from "../Array.ts" |
| 15 | import type * as Context from "../Context.ts" |
| 16 | import type * as Option from "../Option.ts" |
| 17 | |
| 18 | /** |
| 19 | * Structured representation of help documentation for a command. |
| 20 | * This data structure is independent of formatting, allowing for |
| 21 | * different output formats (text, markdown, JSON, etc.). |
| 22 | * |
| 23 | * **Example** (Defining command help documentation) |
| 24 | * |
| 25 | * ```ts import.meta.vitest |
| 26 | * import { Context, Option as O } from "effect" |
| 27 | * import type { HelpDoc } from "effect/cli" |
| 28 | * |
| 29 | * const deployCommandHelp: HelpDoc.HelpDoc = { |
| 30 | * description: "Deploy your application to the cloud", |
| 31 | * usage: "myapp deploy [options] <target>", |
| 32 | * annotations: Context.empty(), |
| 33 | * flags: [ |
| 34 | * { |
| 35 | * name: "verbose", |
| 36 | * aliases: ["-v"], |
| 37 | * type: "boolean", |
| 38 | * description: O.some("Enable verbose logging"), |
| 39 | * required: false |
| 40 | * }, |
| 41 | * { |
| 42 | * name: "env", |
| 43 | * aliases: ["-e"], |
| 44 | * type: "string", |
| 45 | * description: O.some("Target environment"), |
| 46 | * required: true |
| 47 | * } |
| 48 | * ], |
| 49 | * args: [ |
| 50 | * { |
| 51 | * name: "target", |
| 52 | * type: "string", |
| 53 | * description: O.some("Deployment target (e.g., 'production', 'staging')"), |
| 54 | * required: true, |
| 55 | * variadic: false |
| 56 | * } |
| 57 | * ] |
| 58 | * } |
| 59 | * |
| 60 | * deployCommandHelp.usage // => "myapp deploy [options] <target>" |
| 61 | * ``` |
| 62 | * |
| 63 | * @stability unstable |
| 64 | * @category models |
| 65 | * @since 4.0.0 |
| 66 | */ |
| 67 | export interface HelpDoc { |
| 68 | /** |
| 69 | * Brief description of what the command does |
| 70 | */ |
| 71 | readonly description: string |
| 72 | |
| 73 | /** |
| 74 | * Usage syntax showing how to invoke the command |
| 75 | * Example: "myapp deploy [flags]" |
| 76 | */ |
| 77 | readonly usage: string |
| 78 | |
| 79 | /** |
| 80 | * List of available flags/options for this command |
| 81 | */ |
| 82 | readonly flags: ReadonlyArray<FlagDoc> |
| 83 | |
| 84 | /** |
| 85 | * Global flags available to all commands (e.g., --help, --version). |
| 86 | */ |
| 87 | readonly globalFlags?: ReadonlyArray<FlagDoc> |
| 88 | |
| 89 | /** |
| 90 | * Custom command annotations. |
| 91 | */ |
| 92 | readonly annotations: Context.Context<never> |
| 93 | |
| 94 | /** |
| 95 | * List of positional arguments for this command |
| 96 | */ |
| 97 | readonly args?: ReadonlyArray<ArgDoc> |
| 98 | |
| 99 | /** |
| 100 | * Optional list of subcommands if this is a parent command |
| 101 | */ |
| 102 | readonly subcommands?: ReadonlyArray<SubcommandGroupDoc> |
| 103 | |
| 104 | /** |
| 105 | * Optional concrete usage examples for the command |
| 106 | */ |
| 107 | readonly examples?: ReadonlyArray<ExampleDoc> |
| 108 | } |
| 109 | |
| 110 | /** |
| 111 | * Documentation for a command usage example |
| 112 | * |
| 113 | * @stability unstable |
| 114 | * @category models |
| 115 | * @since 4.0.0 |
| 116 | */ |
| 117 | export interface ExampleDoc { |
| 118 | /** |
| 119 | * Command line invocation example |
| 120 | */ |
| 121 | readonly command: string |
| 122 | |
| 123 | /** |
| 124 | * Optional explanation for the example |
| 125 | */ |
| 126 | readonly description?: string | undefined |
| 127 | } |
| 128 | |
| 129 | /** |
| 130 | * Documentation for a single command-line flag/option |
| 131 | * |
| 132 | * **Example** (Documenting command flags) |
| 133 | * |
| 134 | * ```ts import.meta.vitest |
| 135 | * import { Option as O } from "effect" |
| 136 | * import type { HelpDoc } from "effect/cli" |
| 137 | * |
| 138 | * const verboseFlag: HelpDoc.FlagDoc = { |
| 139 | * name: "verbose", |
| 140 | * aliases: ["-v", "--verbose"], |
| 141 | * type: "boolean", |
| 142 | * description: O.some("Enable verbose output"), |
| 143 | * required: false |
| 144 | * } |
| 145 | * |
| 146 | * const portFlag: HelpDoc.FlagDoc = { |
| 147 | * name: "port", |
| 148 | * aliases: ["-p"], |
| 149 | * type: "integer", |
| 150 | * description: O.some("Port number to use"), |
| 151 | * required: true |
| 152 | * } |
| 153 | * |
| 154 | * const names = [verboseFlag.name, portFlag.name] // => ["verbose", "port"] |
| 155 | * ``` |
| 156 | * |
| 157 | * @stability unstable |
| 158 | * @category models |
| 159 | * @since 4.0.0 |
| 160 | */ |
| 161 | export interface FlagDoc { |
| 162 | /** |
| 163 | * Primary name of the flag (e.g., "verbose") |
| 164 | */ |
| 165 | readonly name: string |
| 166 | |
| 167 | /** |
| 168 | * Alternative names/aliases for the flag (e.g., ["-v"]) |
| 169 | */ |
| 170 | readonly aliases: ReadonlyArray<string> |
| 171 | |
| 172 | /** |
| 173 | * Type of the flag value (e.g., "string", "boolean", "integer") |
| 174 | */ |
| 175 | readonly type: string |
| 176 | |
| 177 | /** |
| 178 | * Description of what the flag does |
| 179 | */ |
| 180 | readonly description: Option.Option<string> |
| 181 | |
| 182 | /** |
| 183 | * Whether this flag is required |
| 184 | */ |
| 185 | readonly required: boolean |
| 186 | } |
| 187 | |
| 188 | /** |
| 189 | * Documentation for a subcommand |
| 190 | * |
| 191 | * **Example** (Documenting subcommands) |
| 192 | * |
| 193 | * ```ts import.meta.vitest |
| 194 | * import { Context, Option as O } from "effect" |
| 195 | * import type { HelpDoc } from "effect/cli" |
| 196 | * |
| 197 | * const deploySubcommand: HelpDoc.SubcommandDoc = { |
| 198 | * name: "deploy", |
| 199 | * alias: "d", |
| 200 | * shortDescription: "Deploy app", |
| 201 | * description: "Deploy the application to the cloud" |
| 202 | * } |
| 203 | * |
| 204 | * const buildSubcommand: HelpDoc.SubcommandDoc = { |
| 205 | * name: "build", |
| 206 | * alias: undefined, |
| 207 | * shortDescription: undefined, |
| 208 | * description: "Build the application for production" |
| 209 | * } |
| 210 | * |
| 211 | * // Used in parent command's help documentation |
| 212 | * const mainCommandHelp: HelpDoc.HelpDoc = { |
| 213 | * description: "Cloud deployment tool", |
| 214 | * usage: "myapp <command> [options]", |
| 215 | * annotations: Context.empty(), |
| 216 | * flags: [], |
| 217 | * subcommands: [{ |
| 218 | * group: undefined, |
| 219 | * commands: [deploySubcommand, buildSubcommand] |
| 220 | * }] |
| 221 | * } |
| 222 | * |
| 223 | * mainCommandHelp.subcommands?.[0].commands.map((command) => command.name) // => ["deploy", "build"] |
| 224 | * ``` |
| 225 | * |
| 226 | * @stability unstable |
| 227 | * @category models |
| 228 | * @since 4.0.0 |
| 229 | */ |
| 230 | export interface SubcommandDoc { |
| 231 | /** |
| 232 | * Name of the subcommand |
| 233 | */ |
| 234 | readonly name: string |
| 235 | |
| 236 | /** |
| 237 | * Optional short alias for invoking the subcommand. |
| 238 | */ |
| 239 | readonly alias: string | undefined |
| 240 | |
| 241 | /** |
| 242 | * Optional short description of what the subcommand does. |
| 243 | */ |
| 244 | readonly shortDescription: string | undefined |
| 245 | |
| 246 | /** |
| 247 | * Brief description of what the subcommand does |
| 248 | */ |
| 249 | readonly description: string |
| 250 | } |
| 251 | |
| 252 | /** |
| 253 | * Documentation for a grouped subcommand listing |
| 254 | * |
| 255 | * @stability unstable |
| 256 | * @category models |
| 257 | * @since 4.0.0 |
| 258 | */ |
| 259 | export interface SubcommandGroupDoc { |
| 260 | /** |
| 261 | * Group name used in help output. |
| 262 | * Undefined means the default ungrouped section. |
| 263 | */ |
| 264 | readonly group: string | undefined |
| 265 | |
| 266 | /** |
| 267 | * Subcommands in this group. |
| 268 | */ |
| 269 | readonly commands: NonEmptyReadonlyArray<SubcommandDoc> |
| 270 | } |
| 271 | |
| 272 | /** |
| 273 | * Documentation for a positional argument |
| 274 | * |
| 275 | * **Example** (Documenting positional arguments) |
| 276 | * |
| 277 | * ```ts import.meta.vitest |
| 278 | * import { Context, Option as O } from "effect" |
| 279 | * import type { HelpDoc } from "effect/cli" |
| 280 | * |
| 281 | * const sourceArg: HelpDoc.ArgDoc = { |
| 282 | * name: "source", |
| 283 | * type: "file", |
| 284 | * description: O.some("Source file to process"), |
| 285 | * required: true, |
| 286 | * variadic: false |
| 287 | * } |
| 288 | * |
| 289 | * const filesArg: HelpDoc.ArgDoc = { |
| 290 | * name: "files", |
| 291 | * type: "file", |
| 292 | * description: O.some("Files to process (can specify multiple)"), |
| 293 | * required: false, |
| 294 | * variadic: true |
| 295 | * } |
| 296 | * |
| 297 | * // Used in command help documentation |
| 298 | * const copyCommandHelp: HelpDoc.HelpDoc = { |
| 299 | * description: "Copy files from source to destination", |
| 300 | * usage: "copy <source> [files...]", |
| 301 | * annotations: Context.empty(), |
| 302 | * flags: [], |
| 303 | * args: [sourceArg, filesArg] |
| 304 | * } |
| 305 | * |
| 306 | * copyCommandHelp.args?.map((arg) => arg.name) // => ["source", "files"] |
| 307 | * ``` |
| 308 | * |
| 309 | * @stability unstable |
| 310 | * @category models |
| 311 | * @since 4.0.0 |
| 312 | */ |
| 313 | export interface ArgDoc { |
| 314 | /** |
| 315 | * Name of the argument (e.g., "source", "destination") |
| 316 | */ |
| 317 | readonly name: string |
| 318 | |
| 319 | /** |
| 320 | * Type of the argument value (e.g., "string", "file", "directory") |
| 321 | */ |
| 322 | readonly type: string |
| 323 | |
| 324 | /** |
| 325 | * Description of what the argument is for |
| 326 | */ |
| 327 | readonly description: Option.Option<string> |
| 328 | |
| 329 | /** |
| 330 | * Whether this argument is required or optional |
| 331 | */ |
| 332 | readonly required: boolean |
| 333 | |
| 334 | /** |
| 335 | * Whether this argument is variadic (accepts multiple values) |
| 336 | */ |
| 337 | readonly variadic: boolean |
| 338 | } |