cli-kiss 0.1.8 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1308 -5
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/lib/Command.ts +226 -0
- package/src/lib/Operation.ts +108 -0
- package/src/lib/Option.ts +164 -0
- package/src/lib/Positional.ts +139 -0
- package/src/lib/Reader.ts +110 -0
- package/src/lib/Run.ts +86 -22
- package/src/lib/Type.ts +223 -0
- package/src/lib/Typo.ts +225 -0
- package/src/lib/Usage.ts +42 -0
- package/tests/unit.runner.cycle.ts +6 -6
- package/tests/unit.runner.errors.ts +33 -33
package/src/lib/Command.ts
CHANGED
|
@@ -10,45 +10,185 @@ import {
|
|
|
10
10
|
TypoText,
|
|
11
11
|
} from "./Typo";
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Describes a CLI command: how to parse its arguments from raw CLI input and how to
|
|
15
|
+
* execute it within a given context.
|
|
16
|
+
*
|
|
17
|
+
* A `CommandDescriptor` is the central building block of a `cli-kiss` CLI. You create
|
|
18
|
+
* one with {@link command}, {@link commandWithSubcommands}, or {@link commandChained},
|
|
19
|
+
* and pass it to {@link runAndExit} to run your CLI.
|
|
20
|
+
*
|
|
21
|
+
* @typeParam Context - The value passed into the command when it is executed. It flows
|
|
22
|
+
* from {@link runAndExit}'s `context` argument down through the command chain.
|
|
23
|
+
* @typeParam Result - The value produced by executing the command. For root commands
|
|
24
|
+
* passed to {@link runAndExit} this is always `void`.
|
|
25
|
+
*/
|
|
13
26
|
export type CommandDescriptor<Context, Result> = {
|
|
27
|
+
/** Returns the static metadata (description, hint, details) for this command. */
|
|
14
28
|
getInformation(): CommandInformation;
|
|
29
|
+
/**
|
|
30
|
+
* Parses `readerArgs` and returns a {@link CommandFactory} that can generate usage
|
|
31
|
+
* information or create a ready-to-run {@link CommandInstance}.
|
|
32
|
+
*
|
|
33
|
+
* Parsing errors are captured and deferred: `createFactory` never throws; instead
|
|
34
|
+
* the error surfaces when {@link CommandFactory.createInstance} is called on the
|
|
35
|
+
* returned factory.
|
|
36
|
+
*/
|
|
15
37
|
createFactory(readerArgs: ReaderArgs): CommandFactory<Context, Result>;
|
|
16
38
|
};
|
|
17
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Produced by {@link CommandDescriptor.createFactory} after the raw CLI arguments have
|
|
42
|
+
* been parsed. Provides two capabilities:
|
|
43
|
+
*
|
|
44
|
+
* 1. **Usage generation** — always available, even when parsing failed.
|
|
45
|
+
* 2. **Instance creation** — throws a {@link TypoError} if parsing failed.
|
|
46
|
+
*
|
|
47
|
+
* @typeParam Context - Forwarded from the parent {@link CommandDescriptor}.
|
|
48
|
+
* @typeParam Result - Forwarded from the parent {@link CommandDescriptor}.
|
|
49
|
+
*/
|
|
18
50
|
export type CommandFactory<Context, Result> = {
|
|
51
|
+
/**
|
|
52
|
+
* Builds the complete {@link CommandUsage} for the currently parsed command path.
|
|
53
|
+
* This is called to render the `--help` output and on error when `usageOnError`
|
|
54
|
+
* is enabled.
|
|
55
|
+
*/
|
|
19
56
|
generateUsage(): CommandUsage;
|
|
57
|
+
/**
|
|
58
|
+
* Creates a {@link CommandInstance} that is ready to execute.
|
|
59
|
+
*
|
|
60
|
+
* @throws {@link TypoError} if the argument parsing that occurred during
|
|
61
|
+
* {@link CommandDescriptor.createFactory} encountered an error (e.g. unknown
|
|
62
|
+
* option, missing required positional, invalid type).
|
|
63
|
+
*/
|
|
20
64
|
createInstance(): CommandInstance<Context, Result>;
|
|
21
65
|
};
|
|
22
66
|
|
|
67
|
+
/**
|
|
68
|
+
* A fully parsed, ready-to-execute command.
|
|
69
|
+
*
|
|
70
|
+
* @typeParam Context - The value the caller must provide when executing the command.
|
|
71
|
+
* @typeParam Result - The value the command produces on successful execution.
|
|
72
|
+
*/
|
|
23
73
|
export type CommandInstance<Context, Result> = {
|
|
74
|
+
/**
|
|
75
|
+
* Executes the command with the provided context.
|
|
76
|
+
*
|
|
77
|
+
* @param context - Arbitrary value injected by the caller (see {@link runAndExit}).
|
|
78
|
+
* @returns A promise that resolves to the command's result, or rejects if the
|
|
79
|
+
* command handler throws.
|
|
80
|
+
*/
|
|
24
81
|
executeWithContext(context: Context): Promise<Result>;
|
|
25
82
|
};
|
|
26
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Static, human-readable metadata attached to a command.
|
|
86
|
+
*
|
|
87
|
+
* This information is displayed in the usage/help output produced by {@link usageToStyledLines}.
|
|
88
|
+
*/
|
|
27
89
|
export type CommandInformation = {
|
|
90
|
+
/** Short description of what the command does. Shown prominently in the usage header. */
|
|
28
91
|
description: string;
|
|
92
|
+
/**
|
|
93
|
+
* Optional supplementary note shown in parentheses next to the description.
|
|
94
|
+
* Suitable for short caveats such as `"deprecated"` or `"experimental"`.
|
|
95
|
+
*/
|
|
29
96
|
hint?: string;
|
|
97
|
+
/**
|
|
98
|
+
* Optional list of additional detail lines printed below the description.
|
|
99
|
+
* Useful for multi-line explanations, examples, or caveats that don't fit in
|
|
100
|
+
* a single sentence.
|
|
101
|
+
*/
|
|
30
102
|
details?: Array<string>;
|
|
31
103
|
// TODO - printable examples ?
|
|
32
104
|
};
|
|
33
105
|
|
|
106
|
+
/**
|
|
107
|
+
* The full usage/help model for a command as it appears after argument parsing.
|
|
108
|
+
*
|
|
109
|
+
* This is produced by {@link CommandFactory.generateUsage} and consumed by
|
|
110
|
+
* {@link usageToStyledLines} to render the `--help` output.
|
|
111
|
+
*/
|
|
34
112
|
export type CommandUsage = {
|
|
113
|
+
/**
|
|
114
|
+
* Ordered list of breadcrumb segments that form the command's usage line, e.g.:
|
|
115
|
+
* `Usage: my-cli <POSITIONAL> subcommand <ANOTHER_POSITIONAL>`.
|
|
116
|
+
*
|
|
117
|
+
* Each element is either a positional placeholder or a literal subcommand name.
|
|
118
|
+
*/
|
|
35
119
|
breadcrumbs: Array<CommandUsageBreadcrumb>;
|
|
120
|
+
/** The command's static metadata (description, hint, details). */
|
|
36
121
|
information: CommandInformation;
|
|
122
|
+
/**
|
|
123
|
+
* Positional arguments that belong to the current command path,
|
|
124
|
+
* in the order they must appear on the command line.
|
|
125
|
+
*/
|
|
37
126
|
positionals: Array<PositionalUsage>;
|
|
127
|
+
/**
|
|
128
|
+
* Subcommands available at the current level of the command hierarchy.
|
|
129
|
+
* Non-empty only when the command is a {@link commandWithSubcommands} and the
|
|
130
|
+
* subcommand selection could not be resolved (i.e. on error or `--help`).
|
|
131
|
+
*/
|
|
38
132
|
subcommands: Array<CommandUsageSubcommand>;
|
|
133
|
+
/**
|
|
134
|
+
* Options (flags and valued options) accepted by the current command path,
|
|
135
|
+
* in the order they were registered.
|
|
136
|
+
*/
|
|
39
137
|
options: Array<OptionUsage>;
|
|
40
138
|
};
|
|
41
139
|
|
|
140
|
+
/**
|
|
141
|
+
* A single element in the usage breadcrumb trail shown at the top of the help output.
|
|
142
|
+
*
|
|
143
|
+
* - `{ positional: string }` — A positional placeholder such as `<NAME>` or `[FILE]`.
|
|
144
|
+
* - `{ command: string }` — A literal subcommand token such as `deploy`.
|
|
145
|
+
*/
|
|
42
146
|
export type CommandUsageBreadcrumb =
|
|
43
147
|
| { positional: string }
|
|
44
148
|
| { command: string };
|
|
45
149
|
|
|
150
|
+
/**
|
|
151
|
+
* Summary information about a single subcommand shown in the `Subcommands:` section
|
|
152
|
+
* of the usage output.
|
|
153
|
+
*/
|
|
46
154
|
export type CommandUsageSubcommand = {
|
|
155
|
+
/** The literal token the user types to select this subcommand (e.g. `"deploy"`). */
|
|
47
156
|
name: string;
|
|
157
|
+
/** Short description forwarded from the subcommand's {@link CommandInformation}. */
|
|
48
158
|
description: string | undefined;
|
|
159
|
+
/** Optional hint forwarded from the subcommand's {@link CommandInformation}. */
|
|
49
160
|
hint: string | undefined;
|
|
50
161
|
};
|
|
51
162
|
|
|
163
|
+
/**
|
|
164
|
+
* Creates a leaf command — a command that has no subcommands and directly executes
|
|
165
|
+
* an {@link OperationDescriptor}.
|
|
166
|
+
*
|
|
167
|
+
* During parsing, `command` reads all positionals and options consumed by `operation`,
|
|
168
|
+
* then asserts that no extra positionals remain. Any unexpected trailing positional
|
|
169
|
+
* causes a {@link TypoError} deferred to {@link CommandFactory.createInstance}.
|
|
170
|
+
*
|
|
171
|
+
* @typeParam Context - The context value forwarded to the operation handler at
|
|
172
|
+
* execution time.
|
|
173
|
+
* @typeParam Result - The value returned by the operation handler.
|
|
174
|
+
*
|
|
175
|
+
* @param information - Static metadata (description, hint, details) for the command.
|
|
176
|
+
* @param operation - The operation that defines options, positionals, and the execution
|
|
177
|
+
* handler for this command.
|
|
178
|
+
* @returns A {@link CommandDescriptor} suitable for passing to {@link runAndExit}
|
|
179
|
+
* or composing with {@link commandWithSubcommands} / {@link commandChained}.
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* ```ts
|
|
183
|
+
* const greet = command(
|
|
184
|
+
* { description: "Greet a user" },
|
|
185
|
+
* operation(
|
|
186
|
+
* { options: {}, positionals: [positionalRequired({ type: typeString, label: "NAME" })] },
|
|
187
|
+
* async (_ctx, { positionals: [name] }) => console.log(`Hello, ${name}!`),
|
|
188
|
+
* ),
|
|
189
|
+
* );
|
|
190
|
+
* ```
|
|
191
|
+
*/
|
|
52
192
|
export function command<Context, Result>(
|
|
53
193
|
information: CommandInformation,
|
|
54
194
|
operation: OperationDescriptor<Context, Result>,
|
|
@@ -104,6 +244,48 @@ export function command<Context, Result>(
|
|
|
104
244
|
};
|
|
105
245
|
}
|
|
106
246
|
|
|
247
|
+
/**
|
|
248
|
+
* Creates a command that first runs an {@link OperationDescriptor} to produce an
|
|
249
|
+
* intermediate `Payload`, then dispatches execution to one of several named subcommands
|
|
250
|
+
* based on the next positional argument.
|
|
251
|
+
*
|
|
252
|
+
* **Parsing behaviour:**
|
|
253
|
+
* 1. The `operation`'s positionals and options are parsed from `readerArgs`.
|
|
254
|
+
* 2. The next positional token is consumed as the subcommand name.
|
|
255
|
+
* - If no token is present, a {@link TypoError} is deferred.
|
|
256
|
+
* - If the token does not match any key in `subcommands`, a {@link TypoError} is
|
|
257
|
+
* deferred.
|
|
258
|
+
* 3. The matched subcommand's factory is created with the remaining `readerArgs`.
|
|
259
|
+
*
|
|
260
|
+
* **Usage on error / `--help`:** when the subcommand cannot be determined, the usage
|
|
261
|
+
* output lists all available subcommands under a `Subcommands:` section.
|
|
262
|
+
*
|
|
263
|
+
* @typeParam Context - The context value accepted by the root operation handler.
|
|
264
|
+
* @typeParam Payload - The value produced by the root operation and forwarded as the
|
|
265
|
+
* context to the selected subcommand.
|
|
266
|
+
* @typeParam Result - The value produced by the selected subcommand.
|
|
267
|
+
*
|
|
268
|
+
* @param information - Static metadata shown in the top-level usage when no valid
|
|
269
|
+
* subcommand has been selected.
|
|
270
|
+
* @param operation - The operation that is always executed first, before the
|
|
271
|
+
* subcommand. Its output becomes the subcommand's context.
|
|
272
|
+
* @param subcommands - A map of lowercase subcommand names to their
|
|
273
|
+
* {@link CommandDescriptor}s. The keys are the literal tokens the user types.
|
|
274
|
+
* @returns A {@link CommandDescriptor} that dispatches to one of the provided
|
|
275
|
+
* subcommands.
|
|
276
|
+
*
|
|
277
|
+
* @example
|
|
278
|
+
* ```ts
|
|
279
|
+
* const rootCmd = commandWithSubcommands(
|
|
280
|
+
* { description: "My CLI" },
|
|
281
|
+
* operation({ options: {}, positionals: [] }, async (ctx) => ctx),
|
|
282
|
+
* {
|
|
283
|
+
* deploy: command({ description: "Deploy" }, deployOperation),
|
|
284
|
+
* rollback: command({ description: "Rollback" }, rollbackOperation),
|
|
285
|
+
* },
|
|
286
|
+
* );
|
|
287
|
+
* ```
|
|
288
|
+
*/
|
|
107
289
|
export function commandWithSubcommands<Context, Payload, Result>(
|
|
108
290
|
information: CommandInformation,
|
|
109
291
|
operation: OperationDescriptor<Context, Payload>,
|
|
@@ -198,6 +380,50 @@ export function commandWithSubcommands<Context, Payload, Result>(
|
|
|
198
380
|
};
|
|
199
381
|
}
|
|
200
382
|
|
|
383
|
+
/**
|
|
384
|
+
* Creates a command that chains two command stages by piping the output of an
|
|
385
|
+
* {@link OperationDescriptor} directly into a {@link CommandDescriptor} as its context.
|
|
386
|
+
*
|
|
387
|
+
* Unlike {@link commandWithSubcommands}, there is no runtime token consumed for routing;
|
|
388
|
+
* the `nextCommand` is always the continuation. This is useful for splitting a complex
|
|
389
|
+
* command into reusable pieces, such as a shared authentication step followed by
|
|
390
|
+
* different sub-actions.
|
|
391
|
+
*
|
|
392
|
+
* **Parsing behaviour:**
|
|
393
|
+
* 1. `operation`'s positionals and options are parsed.
|
|
394
|
+
* 2. `nextCommand`'s factory is immediately created from the same `readerArgs` (the
|
|
395
|
+
* remaining unparsed tokens).
|
|
396
|
+
* 3. At execution time, `operation` runs first; its result is passed as the context to
|
|
397
|
+
* `nextCommand`.
|
|
398
|
+
*
|
|
399
|
+
* **Usage:** breadcrumbs, positionals, and options from both stages are merged into a
|
|
400
|
+
* single flat usage description. The `information` of `nextCommand` takes precedence in
|
|
401
|
+
* the generated usage output.
|
|
402
|
+
*
|
|
403
|
+
* @typeParam Context - The context value accepted by `operation`.
|
|
404
|
+
* @typeParam Payload - The value produced by `operation` and used as the context for
|
|
405
|
+
* `nextCommand`.
|
|
406
|
+
* @typeParam Result - The value produced by `nextCommand`.
|
|
407
|
+
*
|
|
408
|
+
* @param information - Fallback metadata used in the usage output when `nextCommand`'s
|
|
409
|
+
* factory cannot be created (i.e. on parse error in the next stage).
|
|
410
|
+
* @param operation - The first stage operation. Its output becomes `nextCommand`'s
|
|
411
|
+
* context.
|
|
412
|
+
* @param nextCommand - The second stage command, executed after `operation` succeeds.
|
|
413
|
+
* @returns A {@link CommandDescriptor} that transparently composes the two stages.
|
|
414
|
+
*
|
|
415
|
+
* @example
|
|
416
|
+
* ```ts
|
|
417
|
+
* const authenticatedDeploy = commandChained(
|
|
418
|
+
* { description: "Authenticate then deploy" },
|
|
419
|
+
* operation(
|
|
420
|
+
* { options: { token: optionSingleValue({ long: "token", type: typeString, default: () => "" }) }, positionals: [] },
|
|
421
|
+
* async (_ctx, { options: { token } }) => ({ token }),
|
|
422
|
+
* ),
|
|
423
|
+
* command({ description: "Deploy" }, deployOperation),
|
|
424
|
+
* );
|
|
425
|
+
* ```
|
|
426
|
+
*/
|
|
201
427
|
export function commandChained<Context, Payload, Result>(
|
|
202
428
|
information: CommandInformation,
|
|
203
429
|
operation: OperationDescriptor<Context, Payload>,
|
package/src/lib/Operation.ts
CHANGED
|
@@ -2,24 +2,132 @@ import { Option, OptionUsage } from "./Option";
|
|
|
2
2
|
import { Positional, PositionalUsage } from "./Positional";
|
|
3
3
|
import { ReaderArgs } from "./Reader";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Describes an operation — the combination of options, positional arguments, and an
|
|
7
|
+
* async execution handler that together form the core logic of a CLI command.
|
|
8
|
+
*
|
|
9
|
+
* An `OperationDescriptor` is created with {@link operation} and passed to
|
|
10
|
+
* {@link command}, {@link commandWithSubcommands}, or {@link commandChained} to build
|
|
11
|
+
* a full {@link CommandDescriptor}.
|
|
12
|
+
*
|
|
13
|
+
* @typeParam Input - The context value the handler receives at execution time (forwarded
|
|
14
|
+
* from the parent command's context or from a preceding chained operation).
|
|
15
|
+
* @typeParam Output - The value the handler produces. For leaf operations this is
|
|
16
|
+
* typically `void`; for intermediate stages it is the payload forwarded to the next
|
|
17
|
+
* command in a chain.
|
|
18
|
+
*/
|
|
5
19
|
export type OperationDescriptor<Input, Output> = {
|
|
20
|
+
/**
|
|
21
|
+
* Returns usage metadata (options and positionals) without consuming any arguments.
|
|
22
|
+
* Called by the parent command factory when building the help/usage output.
|
|
23
|
+
*/
|
|
6
24
|
generateUsage(): OperationUsage;
|
|
25
|
+
/**
|
|
26
|
+
* Parses options and positionals from `readerArgs` and returns an
|
|
27
|
+
* {@link OperationFactory} that can create a ready-to-execute
|
|
28
|
+
* {@link OperationInstance}.
|
|
29
|
+
*
|
|
30
|
+
* Any parse error (unknown option, type mismatch, etc.) is captured and re-thrown
|
|
31
|
+
* when {@link OperationFactory.createInstance} is called.
|
|
32
|
+
*
|
|
33
|
+
* @param readerArgs - The shared argument reader. Options are registered on it and
|
|
34
|
+
* positionals are consumed in declaration order.
|
|
35
|
+
*/
|
|
7
36
|
createFactory(readerArgs: ReaderArgs): OperationFactory<Input, Output>;
|
|
8
37
|
};
|
|
9
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Produced by {@link OperationDescriptor.createFactory} after argument parsing.
|
|
41
|
+
* Instantiating it finalises value extraction and produces an {@link OperationInstance}.
|
|
42
|
+
*
|
|
43
|
+
* @typeParam Input - Forwarded from the parent {@link OperationDescriptor}.
|
|
44
|
+
* @typeParam Output - Forwarded from the parent {@link OperationDescriptor}.
|
|
45
|
+
*/
|
|
10
46
|
export type OperationFactory<Input, Output> = {
|
|
47
|
+
/**
|
|
48
|
+
* Extracts the final parsed values for all options and returns an
|
|
49
|
+
* {@link OperationInstance} ready for execution.
|
|
50
|
+
*
|
|
51
|
+
* @throws {@link TypoError} if any option or positional validation failed during
|
|
52
|
+
* {@link OperationDescriptor.createFactory}.
|
|
53
|
+
*/
|
|
11
54
|
createInstance(): OperationInstance<Input, Output>;
|
|
12
55
|
};
|
|
13
56
|
|
|
57
|
+
/**
|
|
58
|
+
* A fully parsed, ready-to-execute operation.
|
|
59
|
+
*
|
|
60
|
+
* @typeParam Input - The value the caller must supply as context.
|
|
61
|
+
* @typeParam Output - The value produced on successful execution.
|
|
62
|
+
*/
|
|
14
63
|
export type OperationInstance<Input, Output> = {
|
|
64
|
+
/**
|
|
65
|
+
* Runs the operation handler with the provided input context and the parsed
|
|
66
|
+
* option/positional values.
|
|
67
|
+
*
|
|
68
|
+
* @param input - Context from the parent command (or the root context supplied to
|
|
69
|
+
* {@link runAndExit}).
|
|
70
|
+
* @returns A promise resolving to the handler's return value.
|
|
71
|
+
*/
|
|
15
72
|
executeWithContext(input: Input): Promise<Output>;
|
|
16
73
|
};
|
|
17
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Collected usage metadata produced by {@link OperationDescriptor.generateUsage}.
|
|
77
|
+
* Consumed by the parent command factory when building {@link CommandUsage}.
|
|
78
|
+
*/
|
|
18
79
|
export type OperationUsage = {
|
|
80
|
+
/** Usage descriptors for all options registered by this operation. */
|
|
19
81
|
options: Array<OptionUsage>;
|
|
82
|
+
/** Usage descriptors for all positionals declared by this operation, in order. */
|
|
20
83
|
positionals: Array<PositionalUsage>;
|
|
21
84
|
};
|
|
22
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Creates an {@link OperationDescriptor} from a set of options, positionals, and an
|
|
88
|
+
* async handler function.
|
|
89
|
+
*
|
|
90
|
+
* The `handler` receives:
|
|
91
|
+
* - `context` — the value passed down from the parent command (or from
|
|
92
|
+
* {@link runAndExit}).
|
|
93
|
+
* - `inputs.options` — an object whose keys match those declared in `inputs.options` and whose values are
|
|
94
|
+
* the parsed option values.
|
|
95
|
+
* - `inputs.positionals` — a tuple whose elements match `inputs.positionals` and whose
|
|
96
|
+
* values are the parsed positional values, in declaration order.
|
|
97
|
+
*
|
|
98
|
+
* @typeParam Context - The context type accepted by the handler.
|
|
99
|
+
* @typeParam Result - The return type of the handler.
|
|
100
|
+
* @typeParam Options - Object type mapping option keys to their parsed value types.
|
|
101
|
+
* @typeParam Positionals - Tuple type of parsed positional value types, in order.
|
|
102
|
+
*
|
|
103
|
+
* @param inputs - Declares the options and positionals this operation accepts.
|
|
104
|
+
* @param inputs.options - A map from arbitrary keys to {@link Option} descriptors.
|
|
105
|
+
* The same keys appear in `handler`'s `inputs.options` argument.
|
|
106
|
+
* @param inputs.positionals - An ordered array of {@link Positional} descriptors.
|
|
107
|
+
* Their parsed values appear in `handler`'s `inputs.positionals` argument, in the
|
|
108
|
+
* same order.
|
|
109
|
+
* @param handler - The async function that implements the command logic. Receives the
|
|
110
|
+
* execution context and all parsed inputs.
|
|
111
|
+
* @returns An {@link OperationDescriptor} ready to be composed into a command.
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```ts
|
|
115
|
+
* const greetOperation = operation(
|
|
116
|
+
* {
|
|
117
|
+
* options: {
|
|
118
|
+
* loud: optionFlag({ long: "loud", description: "Print in uppercase" }),
|
|
119
|
+
* },
|
|
120
|
+
* positionals: [
|
|
121
|
+
* positionalRequired({ type: typeString, label: "NAME", description: "Name to greet" }),
|
|
122
|
+
* ],
|
|
123
|
+
* },
|
|
124
|
+
* async (_ctx, { options: { loud }, positionals: [name] }) => {
|
|
125
|
+
* const message = `Hello, ${name}!`;
|
|
126
|
+
* console.log(loud ? message.toUpperCase() : message);
|
|
127
|
+
* },
|
|
128
|
+
* );
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
23
131
|
export function operation<
|
|
24
132
|
Context,
|
|
25
133
|
Result,
|
package/src/lib/Option.ts
CHANGED
|
@@ -9,23 +9,107 @@ import {
|
|
|
9
9
|
TypoText,
|
|
10
10
|
} from "./Typo";
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Describes a single CLI option (a flag or a valued option) together with its parsing
|
|
14
|
+
* and usage-generation logic.
|
|
15
|
+
*
|
|
16
|
+
* Options are created with {@link optionFlag}, {@link optionSingleValue}, or
|
|
17
|
+
* {@link optionRepeatable} and are passed via the `options` map of {@link operation}.
|
|
18
|
+
*
|
|
19
|
+
* @typeParam Value - The TypeScript type of the parsed option value.
|
|
20
|
+
* - `boolean` for flags created with {@link optionFlag}.
|
|
21
|
+
* - `T` for single-value options created with {@link optionSingleValue}.
|
|
22
|
+
* - `Array<T>` for repeatable options created with {@link optionRepeatable}.
|
|
23
|
+
*/
|
|
12
24
|
export type Option<Value> = {
|
|
25
|
+
/** Returns human-readable metadata used to render the `Options:` section of help. */
|
|
13
26
|
generateUsage(): OptionUsage;
|
|
27
|
+
/**
|
|
28
|
+
* Registers the option on `readerOptions` so the argument reader recognises it, and
|
|
29
|
+
* returns an {@link OptionGetter} that can later retrieve the parsed value(s).
|
|
30
|
+
*
|
|
31
|
+
* @param readerOptions - The shared {@link ReaderArgs} that will parse the raw
|
|
32
|
+
* command-line tokens.
|
|
33
|
+
*/
|
|
14
34
|
createGetter(readerOptions: ReaderOptions): OptionGetter<Value>;
|
|
15
35
|
};
|
|
16
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Human-readable metadata for a single CLI option, used to render the `Options:` section
|
|
39
|
+
* of the help output produced by {@link usageToStyledLines}.
|
|
40
|
+
*/
|
|
17
41
|
export type OptionUsage = {
|
|
42
|
+
/** Short description of what the option does. */
|
|
18
43
|
description: string | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Optional supplementary note shown in parentheses next to the description.
|
|
46
|
+
* Suitable for short caveats such as `"required"` or `"defaults to 42"`.
|
|
47
|
+
*/
|
|
19
48
|
hint: string | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* The primary long-form name of the option, without the `--` prefix (e.g. `"verbose"`).
|
|
51
|
+
* The user passes this as `--verbose` on the command line.
|
|
52
|
+
*/
|
|
20
53
|
long: Lowercase<string>; // TODO - better type for long option names ?
|
|
54
|
+
/**
|
|
55
|
+
* The optional short-form name of the option, without the `-` prefix (e.g. `"v"`).
|
|
56
|
+
* The user passes this as `-v` on the command line.
|
|
57
|
+
*/
|
|
21
58
|
short: string | undefined;
|
|
59
|
+
/**
|
|
60
|
+
* The value placeholder label shown after the long option name in the help output
|
|
61
|
+
* (e.g. `"<FILE>"`). `undefined` for flags that take no value.
|
|
62
|
+
*/
|
|
22
63
|
label: Uppercase<string> | undefined;
|
|
23
64
|
};
|
|
24
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Retrieves the parsed value for a registered option after argument parsing is complete.
|
|
68
|
+
*
|
|
69
|
+
* Returned by {@link Option.createGetter} and called by {@link OperationFactory.createInstance}.
|
|
70
|
+
*
|
|
71
|
+
* @typeParam Value - The TypeScript type of the parsed value.
|
|
72
|
+
*/
|
|
25
73
|
export type OptionGetter<Value> = {
|
|
74
|
+
/**
|
|
75
|
+
* Returns the fully decoded and validated value for the option.
|
|
76
|
+
*
|
|
77
|
+
* @throws {@link TypoError} if the option appeared more times than allowed, the value
|
|
78
|
+
* failed type decoding, or a required default could not be computed.
|
|
79
|
+
*/
|
|
26
80
|
getValue(): Value;
|
|
27
81
|
};
|
|
28
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Creates a boolean flag option — an option that the user passes without a value (e.g.
|
|
85
|
+
* `--verbose`) to signal `true`, or can explicitly set with `--flag=true` / `--flag=no`.
|
|
86
|
+
*
|
|
87
|
+
* **Parsing rules:**
|
|
88
|
+
* - Absent → `false` (or the return value of `default()` when provided).
|
|
89
|
+
* - `--flag` / `--flag=true` / `--flag=yes` → `true`.
|
|
90
|
+
* - `--flag=false` / `--flag=no` → `false`.
|
|
91
|
+
* - Specified more than once → {@link TypoError} ("Must not be set multiple times").
|
|
92
|
+
*
|
|
93
|
+
* @param definition - Configuration for the flag.
|
|
94
|
+
* @param definition.long - Primary long-form name (without `--`). Must be lowercase.
|
|
95
|
+
* @param definition.short - Optional short-form name (without `-`).
|
|
96
|
+
* @param definition.description - Human-readable description for the help output.
|
|
97
|
+
* @param definition.hint - Optional supplementary note shown in parentheses.
|
|
98
|
+
* @param definition.aliases - Additional long/short names that the parser also
|
|
99
|
+
* recognises as this flag.
|
|
100
|
+
* @param definition.default - Factory for the default value when the flag is absent.
|
|
101
|
+
* Defaults to `() => false` when omitted.
|
|
102
|
+
* @returns An {@link Option}`<boolean>`.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```ts
|
|
106
|
+
* const verboseFlag = optionFlag({
|
|
107
|
+
* long: "verbose",
|
|
108
|
+
* short: "v",
|
|
109
|
+
* description: "Enable verbose output",
|
|
110
|
+
* });
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
29
113
|
export function optionFlag(definition: {
|
|
30
114
|
long: Lowercase<string>;
|
|
31
115
|
short?: string;
|
|
@@ -82,6 +166,47 @@ export function optionFlag(definition: {
|
|
|
82
166
|
};
|
|
83
167
|
}
|
|
84
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Creates an option that accepts exactly one value (e.g. `--output dist/` or
|
|
171
|
+
* `--output=dist/`).
|
|
172
|
+
*
|
|
173
|
+
* **Parsing rules:**
|
|
174
|
+
* - Absent → `definition.default()` is called. If the default factory throws, a
|
|
175
|
+
* {@link TypoError} is produced.
|
|
176
|
+
* - Specified once → the value is decoded with `definition.type`.
|
|
177
|
+
* - Specified more than once → {@link TypoError} ("Requires a single value, but got
|
|
178
|
+
* multiple").
|
|
179
|
+
*
|
|
180
|
+
* **Value syntax:** `--long value`, `--long=value`, or (if `short` is set) `-s value`,
|
|
181
|
+
* `-s=value`, or `-svalue`.
|
|
182
|
+
*
|
|
183
|
+
* @typeParam Value - The TypeScript type produced by the type decoder.
|
|
184
|
+
*
|
|
185
|
+
* @param definition - Configuration for the option.
|
|
186
|
+
* @param definition.long - Primary long-form name (without `--`). Must be lowercase.
|
|
187
|
+
* @param definition.short - Optional short-form name (without `-`).
|
|
188
|
+
* @param definition.description - Human-readable description for the help output.
|
|
189
|
+
* @param definition.hint - Optional supplementary note shown in parentheses.
|
|
190
|
+
* @param definition.aliases - Additional long/short names the parser also recognises.
|
|
191
|
+
* @param definition.label - Custom label shown in the help output (e.g. `"FILE"`).
|
|
192
|
+
* Defaults to the uppercased `type.content`.
|
|
193
|
+
* @param definition.type - The {@link Type} used to decode the raw string value.
|
|
194
|
+
* @param definition.default - Factory for the default value when the option is absent.
|
|
195
|
+
* Throw an error from this factory to make the option effectively required.
|
|
196
|
+
* @returns An {@link Option}`<Value>`.
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* ```ts
|
|
200
|
+
* const outputOption = optionSingleValue({
|
|
201
|
+
* long: "output",
|
|
202
|
+
* short: "o",
|
|
203
|
+
* type: typeString,
|
|
204
|
+
* label: "PATH",
|
|
205
|
+
* description: "Output directory",
|
|
206
|
+
* default: () => "dist/",
|
|
207
|
+
* });
|
|
208
|
+
* ```
|
|
209
|
+
*/
|
|
85
210
|
export function optionSingleValue<Value>(definition: {
|
|
86
211
|
long: Lowercase<string>;
|
|
87
212
|
short?: string;
|
|
@@ -145,6 +270,45 @@ export function optionSingleValue<Value>(definition: {
|
|
|
145
270
|
};
|
|
146
271
|
}
|
|
147
272
|
|
|
273
|
+
/**
|
|
274
|
+
* Creates an option that can be specified any number of times, collecting all provided
|
|
275
|
+
* values into an array (e.g. `--file a.ts --file b.ts`).
|
|
276
|
+
*
|
|
277
|
+
* **Parsing rules:**
|
|
278
|
+
* - Absent → empty array `[]`.
|
|
279
|
+
* - Specified N times → array of N decoded values, in the order they appear on the
|
|
280
|
+
* command line.
|
|
281
|
+
* - Each occurrence is decoded independently with `definition.type`.
|
|
282
|
+
*
|
|
283
|
+
* **Value syntax:** `--long value`, `--long=value`, or (if `short` is set) `-s value`,
|
|
284
|
+
* `-s=value`, or `-svalue`.
|
|
285
|
+
*
|
|
286
|
+
* @typeParam Value - The TypeScript type produced by the type decoder for each
|
|
287
|
+
* occurrence.
|
|
288
|
+
*
|
|
289
|
+
* @param definition - Configuration for the option.
|
|
290
|
+
* @param definition.long - Primary long-form name (without `--`). Must be lowercase.
|
|
291
|
+
* @param definition.short - Optional short-form name (without `-`).
|
|
292
|
+
* @param definition.description - Human-readable description for the help output.
|
|
293
|
+
* @param definition.hint - Optional supplementary note shown in parentheses.
|
|
294
|
+
* @param definition.aliases - Additional long/short names the parser also recognises.
|
|
295
|
+
* @param definition.label - Custom label shown in the help output (e.g. `"FILE"`).
|
|
296
|
+
* Defaults to the uppercased `type.content`.
|
|
297
|
+
* @param definition.type - The {@link Type} used to decode each raw string value.
|
|
298
|
+
* @returns An {@link Option}`<Array<Value>>`.
|
|
299
|
+
*
|
|
300
|
+
* @example
|
|
301
|
+
* ```ts
|
|
302
|
+
* const filesOption = optionRepeatable({
|
|
303
|
+
* long: "file",
|
|
304
|
+
* short: "f",
|
|
305
|
+
* type: typeString,
|
|
306
|
+
* label: "PATH",
|
|
307
|
+
* description: "Input file (may be repeated)",
|
|
308
|
+
* });
|
|
309
|
+
* // Usage: my-cli --file a.ts --file b.ts → ["a.ts", "b.ts"]
|
|
310
|
+
* ```
|
|
311
|
+
*/
|
|
148
312
|
export function optionRepeatable<Value>(definition: {
|
|
149
313
|
long: Lowercase<string>;
|
|
150
314
|
short?: string;
|