cli-kiss 0.2.1 → 0.2.3
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/README.md +1 -2
- package/dist/index.d.ts +527 -830
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/docs/.vitepress/config.mts +3 -0
- package/docs/guide/01_getting_started.md +9 -9
- package/docs/guide/02_commands.md +6 -11
- package/docs/guide/03_options.md +4 -6
- package/docs/guide/04_positionals.md +6 -8
- package/docs/guide/05_types.md +8 -10
- package/docs/guide/06_run.md +6 -7
- package/docs/index.md +4 -4
- package/package.json +1 -1
- package/src/lib/Command.ts +189 -256
- package/src/lib/Operation.ts +68 -82
- package/src/lib/Option.ts +115 -132
- package/src/lib/Positional.ts +95 -100
- package/src/lib/Reader.ts +53 -78
- package/src/lib/Run.ts +40 -58
- package/src/lib/Type.ts +81 -141
- package/src/lib/Typo.ts +121 -164
- package/src/lib/Usage.ts +56 -18
- package/tests/unit.command.execute.ts +83 -71
- package/tests/unit.command.usage.ts +230 -109
- package/tests/unit.runner.cycle.ts +56 -7
package/src/lib/Command.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Operation } from "./Operation";
|
|
2
2
|
import { OptionUsage } from "./Option";
|
|
3
3
|
import { PositionalUsage } from "./Positional";
|
|
4
4
|
import { ReaderArgs } from "./Reader";
|
|
@@ -11,172 +11,160 @@ import {
|
|
|
11
11
|
} from "./Typo";
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* A CLI command — parses arguments and executes within a given context.
|
|
15
|
+
* Created with {@link command}, {@link commandWithSubcommands}, or {@link commandChained}.
|
|
16
|
+
* Usually passed through {@link runAndExit} to run.
|
|
16
17
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
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`.
|
|
18
|
+
* @typeParam Context - Injected at execution time; forwarded to handlers. Use to inject dependencies.
|
|
19
|
+
* @typeParam Result - Value produced on execution; typically `void` for leaf commands.
|
|
25
20
|
*/
|
|
26
|
-
export type
|
|
27
|
-
/**
|
|
21
|
+
export type Command<Context, Result> = {
|
|
22
|
+
/**
|
|
23
|
+
* Returns the command's static metadata.
|
|
24
|
+
*/
|
|
28
25
|
getInformation(): CommandInformation;
|
|
29
26
|
/**
|
|
30
|
-
*
|
|
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.
|
|
27
|
+
* Consumes args in a `readerArgs` and returns a {@link CommandDecoder}.
|
|
36
28
|
*/
|
|
37
|
-
|
|
29
|
+
consumeAndMakeDecoder(
|
|
30
|
+
readerArgs: ReaderArgs,
|
|
31
|
+
): CommandDecoder<Context, Result>;
|
|
38
32
|
};
|
|
39
33
|
|
|
40
34
|
/**
|
|
41
|
-
* Produced by {@link
|
|
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.
|
|
35
|
+
* Produced by {@link Command.consumeAndMakeDecoder}.
|
|
46
36
|
*
|
|
47
|
-
* @typeParam Context -
|
|
48
|
-
* @typeParam Result -
|
|
37
|
+
* @typeParam Context - See {@link Command}.
|
|
38
|
+
* @typeParam Result - See {@link Command}.
|
|
49
39
|
*/
|
|
50
|
-
export type
|
|
40
|
+
export type CommandDecoder<Context, Result> = {
|
|
51
41
|
/**
|
|
52
|
-
* Builds the
|
|
53
|
-
*
|
|
54
|
-
* is enabled.
|
|
42
|
+
* Builds the {@link CommandUsage} for the current command path.
|
|
43
|
+
* Used for `--help` and `usageOnError`.
|
|
55
44
|
*/
|
|
56
45
|
generateUsage(): CommandUsage;
|
|
57
46
|
/**
|
|
58
|
-
* Creates a {@link
|
|
47
|
+
* Creates a ready-to-execute {@link CommandInterpreter}.
|
|
59
48
|
*
|
|
60
|
-
* @throws {@link TypoError} if
|
|
61
|
-
* {@link CommandDescriptor.createFactory} encountered an error (e.g. unknown
|
|
62
|
-
* option, missing required positional, invalid type).
|
|
49
|
+
* @throws {@link TypoError} if parsing or decoding failed.
|
|
63
50
|
*/
|
|
64
|
-
|
|
51
|
+
decodeAndMakeInterpreter(): CommandInterpreter<Context, Result>;
|
|
65
52
|
};
|
|
66
53
|
|
|
67
54
|
/**
|
|
68
|
-
* A fully parsed, ready-to-execute command.
|
|
55
|
+
* A fully parsed, decoded and ready-to-execute command.
|
|
69
56
|
*
|
|
70
|
-
* @typeParam Context -
|
|
71
|
-
* @typeParam Result -
|
|
57
|
+
* @typeParam Context - Caller-supplied context.
|
|
58
|
+
* @typeParam Result - Value produced on success.
|
|
72
59
|
*/
|
|
73
|
-
export type
|
|
60
|
+
export type CommandInterpreter<Context, Result> = {
|
|
74
61
|
/**
|
|
75
|
-
* Executes
|
|
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.
|
|
62
|
+
* Executes with the provided context.
|
|
80
63
|
*/
|
|
81
64
|
executeWithContext(context: Context): Promise<Result>;
|
|
82
65
|
};
|
|
83
66
|
|
|
84
67
|
/**
|
|
85
|
-
* Static,
|
|
86
|
-
*
|
|
87
|
-
* This information is displayed in the usage/help output produced by {@link usageToStyledLines}.
|
|
68
|
+
* Static metadata for a command, shown in `--help` output via {@link usageToStyledLines}.
|
|
88
69
|
*/
|
|
89
70
|
export type CommandInformation = {
|
|
90
|
-
/**
|
|
71
|
+
/**
|
|
72
|
+
* Short description shown in the usage header.
|
|
73
|
+
*/
|
|
91
74
|
description: string;
|
|
92
75
|
/**
|
|
93
|
-
*
|
|
94
|
-
* Suitable for short caveats such as `"deprecated"` or `"experimental"`.
|
|
76
|
+
* Short note shown in parentheses (e.g. `"deprecated"`, `"experimental"`).
|
|
95
77
|
*/
|
|
96
78
|
hint?: string;
|
|
97
79
|
/**
|
|
98
|
-
*
|
|
99
|
-
* Useful for multi-line explanations, examples, or caveats that don't fit in
|
|
100
|
-
* a single sentence.
|
|
80
|
+
* Extra lines printed below the description.
|
|
101
81
|
*/
|
|
102
82
|
details?: Array<string>;
|
|
103
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Examples shown in the `Examples:` section of the usage output.
|
|
85
|
+
*/
|
|
86
|
+
examples?: Array<{
|
|
87
|
+
/**
|
|
88
|
+
* Explanation shown above the example.
|
|
89
|
+
*/
|
|
90
|
+
explanation: string;
|
|
91
|
+
/**
|
|
92
|
+
* Command line args to show as an example of usage.
|
|
93
|
+
*/
|
|
94
|
+
commandArgs: Array<
|
|
95
|
+
| string
|
|
96
|
+
| { positional: string }
|
|
97
|
+
| { subcommand: string }
|
|
98
|
+
| {
|
|
99
|
+
option:
|
|
100
|
+
| { long: string; value?: string }
|
|
101
|
+
| { short: string; value?: string };
|
|
102
|
+
}
|
|
103
|
+
>;
|
|
104
|
+
}>;
|
|
104
105
|
};
|
|
105
106
|
|
|
106
107
|
/**
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
* {@link usageToStyledLines} to render the `--help` output.
|
|
108
|
+
* Full usage/help model.
|
|
109
|
+
* Produced by {@link CommandDecoder.generateUsage},
|
|
110
|
+
* Consumed by {@link usageToStyledLines}.
|
|
111
111
|
*/
|
|
112
112
|
export type CommandUsage = {
|
|
113
113
|
/**
|
|
114
|
-
*
|
|
115
|
-
* `
|
|
116
|
-
|
|
117
|
-
|
|
114
|
+
* Segments forming the usage line
|
|
115
|
+
* (e.g. `my-cli <POSITIONAL> subcommand <ANOTHER_POSITIONAL>`).
|
|
116
|
+
*/
|
|
117
|
+
segments: Array<CommandUsageSegment>;
|
|
118
|
+
/**
|
|
119
|
+
* Command's static metadata.
|
|
118
120
|
*/
|
|
119
|
-
breadcrumbs: Array<CommandUsageBreadcrumb>;
|
|
120
|
-
/** The command's static metadata (description, hint, details). */
|
|
121
121
|
information: CommandInformation;
|
|
122
122
|
/**
|
|
123
|
-
*
|
|
124
|
-
* in the order they must appear on the command line.
|
|
123
|
+
* Positionals in declaration order.
|
|
125
124
|
*/
|
|
126
125
|
positionals: Array<PositionalUsage>;
|
|
127
126
|
/**
|
|
128
|
-
*
|
|
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`).
|
|
127
|
+
* Available subcommands. Non-empty when subcommand was not specified.
|
|
131
128
|
*/
|
|
132
129
|
subcommands: Array<CommandUsageSubcommand>;
|
|
133
130
|
/**
|
|
134
|
-
* Options
|
|
135
|
-
* in the order they were registered.
|
|
131
|
+
* Options in registration order.
|
|
136
132
|
*/
|
|
137
133
|
options: Array<OptionUsage>;
|
|
138
134
|
};
|
|
139
135
|
|
|
140
136
|
/**
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
* - `{ positional: string }` — A positional placeholder such as `<NAME>` or `[FILE]`.
|
|
144
|
-
* - `{ command: string }` — A literal subcommand token such as `deploy`.
|
|
137
|
+
* One element in the usage segment trail.
|
|
145
138
|
*/
|
|
146
|
-
export type
|
|
147
|
-
| { positional: string }
|
|
148
|
-
| { command: string };
|
|
139
|
+
export type CommandUsageSegment = { positional: string } | { command: string };
|
|
149
140
|
|
|
150
141
|
/**
|
|
151
|
-
*
|
|
152
|
-
* of the usage output.
|
|
142
|
+
* Subcommand entry shown in the `Subcommands:` section of the usage output.
|
|
153
143
|
*/
|
|
154
144
|
export type CommandUsageSubcommand = {
|
|
155
|
-
/**
|
|
145
|
+
/**
|
|
146
|
+
* Literal token the user types (e.g. `"deploy"`).
|
|
147
|
+
*/
|
|
156
148
|
name: string;
|
|
157
|
-
/**
|
|
149
|
+
/**
|
|
150
|
+
* Short description from the subcommand's {@link CommandInformation}.
|
|
151
|
+
*/
|
|
158
152
|
description: string | undefined;
|
|
159
|
-
/**
|
|
153
|
+
/**
|
|
154
|
+
* Hint from the subcommand's {@link CommandInformation}.
|
|
155
|
+
*/
|
|
160
156
|
hint: string | undefined;
|
|
161
157
|
};
|
|
162
158
|
|
|
163
159
|
/**
|
|
164
|
-
* Creates a leaf command
|
|
165
|
-
* an {@link OperationDescriptor}.
|
|
160
|
+
* Creates a leaf command that directly executes an {@link Operation}.
|
|
166
161
|
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
* causes a {@link TypoError} deferred to {@link CommandFactory.createInstance}.
|
|
162
|
+
* @typeParam Context - Context forwarded to the handler.
|
|
163
|
+
* @typeParam Result - Value returned by the handler.
|
|
170
164
|
*
|
|
171
|
-
* @
|
|
172
|
-
*
|
|
173
|
-
* @
|
|
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}.
|
|
165
|
+
* @param information - Command metadata (description, hint, details).
|
|
166
|
+
* @param operation - Defines: options, positionals, and the handler.
|
|
167
|
+
* @returns A {@link Command}.
|
|
180
168
|
*
|
|
181
169
|
* @example
|
|
182
170
|
* ```ts
|
|
@@ -191,27 +179,15 @@ export type CommandUsageSubcommand = {
|
|
|
191
179
|
*/
|
|
192
180
|
export function command<Context, Result>(
|
|
193
181
|
information: CommandInformation,
|
|
194
|
-
operation:
|
|
195
|
-
):
|
|
182
|
+
operation: Operation<Context, Result>,
|
|
183
|
+
): Command<Context, Result> {
|
|
196
184
|
return {
|
|
197
185
|
getInformation() {
|
|
198
186
|
return information;
|
|
199
187
|
},
|
|
200
|
-
|
|
201
|
-
function generateUsage(): CommandUsage {
|
|
202
|
-
const operationUsage = operation.generateUsage();
|
|
203
|
-
return {
|
|
204
|
-
breadcrumbs: operationUsage.positionals.map((positional) =>
|
|
205
|
-
breadcrumbPositional(positional.label),
|
|
206
|
-
),
|
|
207
|
-
information: information,
|
|
208
|
-
positionals: operationUsage.positionals,
|
|
209
|
-
subcommands: [],
|
|
210
|
-
options: operationUsage.options,
|
|
211
|
-
};
|
|
212
|
-
}
|
|
188
|
+
consumeAndMakeDecoder(readerArgs: ReaderArgs) {
|
|
213
189
|
try {
|
|
214
|
-
const
|
|
190
|
+
const operationDecoder = operation.consumeAndMakeDecoder(readerArgs);
|
|
215
191
|
const endPositional = readerArgs.consumePositional();
|
|
216
192
|
if (endPositional !== undefined) {
|
|
217
193
|
throw new TypoError(
|
|
@@ -222,20 +198,21 @@ export function command<Context, Result>(
|
|
|
222
198
|
);
|
|
223
199
|
}
|
|
224
200
|
return {
|
|
225
|
-
generateUsage,
|
|
226
|
-
|
|
227
|
-
const
|
|
201
|
+
generateUsage: () => generateUsageShallow(information, operation),
|
|
202
|
+
decodeAndMakeInterpreter() {
|
|
203
|
+
const operationInterpreter =
|
|
204
|
+
operationDecoder.decodeAndMakeInterpreter();
|
|
228
205
|
return {
|
|
229
206
|
async executeWithContext(context: Context) {
|
|
230
|
-
return await
|
|
207
|
+
return await operationInterpreter.executeWithContext(context);
|
|
231
208
|
},
|
|
232
209
|
};
|
|
233
210
|
},
|
|
234
211
|
};
|
|
235
212
|
} catch (error) {
|
|
236
213
|
return {
|
|
237
|
-
generateUsage,
|
|
238
|
-
|
|
214
|
+
generateUsage: () => generateUsageShallow(information, operation),
|
|
215
|
+
decodeAndMakeInterpreter() {
|
|
239
216
|
throw error;
|
|
240
217
|
},
|
|
241
218
|
};
|
|
@@ -245,34 +222,17 @@ export function command<Context, Result>(
|
|
|
245
222
|
}
|
|
246
223
|
|
|
247
224
|
/**
|
|
248
|
-
* Creates a command that
|
|
249
|
-
*
|
|
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.
|
|
225
|
+
* Creates a command that runs an {@link Operation} to produce a `Payload`,
|
|
226
|
+
* then dispatches to a named subcommand based on the next positional token.
|
|
262
227
|
*
|
|
263
|
-
* @typeParam Context -
|
|
264
|
-
* @typeParam Payload -
|
|
265
|
-
*
|
|
266
|
-
* @typeParam Result - The value produced by the selected subcommand.
|
|
228
|
+
* @typeParam Context - Context accepted by `operation`.
|
|
229
|
+
* @typeParam Payload - Output of `operation`; becomes the subcommand's context.
|
|
230
|
+
* @typeParam Result - Value produced by the selected subcommand.
|
|
267
231
|
*
|
|
268
|
-
* @param information -
|
|
269
|
-
*
|
|
270
|
-
* @param
|
|
271
|
-
*
|
|
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.
|
|
232
|
+
* @param information - Command metadata (description, hint, details).
|
|
233
|
+
* @param operation - Always runs first; its output becomes the subcommand's context.
|
|
234
|
+
* @param subcommands - Map of subcommand names to their {@link Command}s.
|
|
235
|
+
* @returns A {@link Command} that dispatches to one of the provided subcommands.
|
|
276
236
|
*
|
|
277
237
|
* @example
|
|
278
238
|
* ```ts
|
|
@@ -288,18 +248,16 @@ export function command<Context, Result>(
|
|
|
288
248
|
*/
|
|
289
249
|
export function commandWithSubcommands<Context, Payload, Result>(
|
|
290
250
|
information: CommandInformation,
|
|
291
|
-
operation:
|
|
292
|
-
subcommands: {
|
|
293
|
-
|
|
294
|
-
},
|
|
295
|
-
): CommandDescriptor<Context, Result> {
|
|
251
|
+
operation: Operation<Context, Payload>,
|
|
252
|
+
subcommands: { [subcommand: Lowercase<string>]: Command<Payload, Result> },
|
|
253
|
+
): Command<Context, Result> {
|
|
296
254
|
return {
|
|
297
255
|
getInformation() {
|
|
298
256
|
return information;
|
|
299
257
|
},
|
|
300
|
-
|
|
258
|
+
consumeAndMakeDecoder(readerArgs: ReaderArgs) {
|
|
301
259
|
try {
|
|
302
|
-
const
|
|
260
|
+
const operationDecoder = operation.consumeAndMakeDecoder(readerArgs);
|
|
303
261
|
const subcommandName = readerArgs.consumePositional();
|
|
304
262
|
if (subcommandName === undefined) {
|
|
305
263
|
throw new TypoError(
|
|
@@ -320,31 +278,29 @@ export function commandWithSubcommands<Context, Payload, Result>(
|
|
|
320
278
|
),
|
|
321
279
|
);
|
|
322
280
|
}
|
|
323
|
-
const
|
|
281
|
+
const subcommandDecoder =
|
|
282
|
+
subcommandInput.consumeAndMakeDecoder(readerArgs);
|
|
324
283
|
return {
|
|
325
284
|
generateUsage() {
|
|
326
|
-
const
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
subcommandUsage.positionals,
|
|
336
|
-
),
|
|
337
|
-
subcommands: subcommandUsage.subcommands,
|
|
338
|
-
options: operationUsage.options.concat(subcommandUsage.options),
|
|
339
|
-
};
|
|
285
|
+
const subcommandUsage = subcommandDecoder.generateUsage();
|
|
286
|
+
const currentUsage = generateUsageShallow(information, operation);
|
|
287
|
+
currentUsage.segments.push(segmentCommand(subcommandName));
|
|
288
|
+
currentUsage.segments.push(...subcommandUsage.segments);
|
|
289
|
+
currentUsage.information = subcommandUsage.information;
|
|
290
|
+
currentUsage.positionals.push(...subcommandUsage.positionals);
|
|
291
|
+
currentUsage.subcommands = subcommandUsage.subcommands;
|
|
292
|
+
currentUsage.options.push(...subcommandUsage.options);
|
|
293
|
+
return currentUsage;
|
|
340
294
|
},
|
|
341
|
-
|
|
342
|
-
const
|
|
343
|
-
|
|
295
|
+
decodeAndMakeInterpreter() {
|
|
296
|
+
const operationInterpreter =
|
|
297
|
+
operationDecoder.decodeAndMakeInterpreter();
|
|
298
|
+
const subcommandInterpreter =
|
|
299
|
+
subcommandDecoder.decodeAndMakeInterpreter();
|
|
344
300
|
return {
|
|
345
301
|
async executeWithContext(context: Context) {
|
|
346
|
-
return await
|
|
347
|
-
await
|
|
302
|
+
return await subcommandInterpreter.executeWithContext(
|
|
303
|
+
await operationInterpreter.executeWithContext(context),
|
|
348
304
|
);
|
|
349
305
|
},
|
|
350
306
|
};
|
|
@@ -353,25 +309,15 @@ export function commandWithSubcommands<Context, Payload, Result>(
|
|
|
353
309
|
} catch (error) {
|
|
354
310
|
return {
|
|
355
311
|
generateUsage() {
|
|
356
|
-
const
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
subcommands: Object.entries(subcommands).map((subcommand) => {
|
|
364
|
-
const metadata = subcommand[1].getInformation();
|
|
365
|
-
return {
|
|
366
|
-
name: subcommand[0],
|
|
367
|
-
description: metadata.description,
|
|
368
|
-
hint: metadata.hint,
|
|
369
|
-
};
|
|
370
|
-
}),
|
|
371
|
-
options: operationUsage.options,
|
|
372
|
-
};
|
|
312
|
+
const currentUsage = generateUsageShallow(information, operation);
|
|
313
|
+
currentUsage.segments.push(segmentPositional("<SUBCOMMAND>"));
|
|
314
|
+
for (const [name, subcommand] of Object.entries(subcommands)) {
|
|
315
|
+
const { description, hint } = subcommand.getInformation();
|
|
316
|
+
currentUsage.subcommands.push({ name, description, hint });
|
|
317
|
+
}
|
|
318
|
+
return currentUsage;
|
|
373
319
|
},
|
|
374
|
-
|
|
320
|
+
decodeAndMakeInterpreter() {
|
|
375
321
|
throw error;
|
|
376
322
|
},
|
|
377
323
|
};
|
|
@@ -381,36 +327,17 @@ export function commandWithSubcommands<Context, Payload, Result>(
|
|
|
381
327
|
}
|
|
382
328
|
|
|
383
329
|
/**
|
|
384
|
-
*
|
|
385
|
-
*
|
|
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.
|
|
330
|
+
* Chains an {@link Operation} and a {@link Command}: `operation` runs first, its
|
|
331
|
+
* output becomes `subcommand`'s context. No token is consumed for routing.
|
|
391
332
|
*
|
|
392
|
-
*
|
|
393
|
-
*
|
|
394
|
-
*
|
|
395
|
-
* remaining unparsed tokens).
|
|
396
|
-
* 3. At execution time, `operation` runs first; its result is passed as the context to
|
|
397
|
-
* `nextCommand`.
|
|
333
|
+
* @typeParam Context - Context accepted by `operation`.
|
|
334
|
+
* @typeParam Payload - Output of `operation`; becomes `subcommand`'s context.
|
|
335
|
+
* @typeParam Result - Value produced by `subcommand`.
|
|
398
336
|
*
|
|
399
|
-
*
|
|
400
|
-
*
|
|
401
|
-
*
|
|
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.
|
|
337
|
+
* @param information - Command metadata (description, hint, details).
|
|
338
|
+
* @param operation - First stage; its output is passed as `subcommand`'s context.
|
|
339
|
+
* @param subcommand - Second stage, executed after `operation`.
|
|
340
|
+
* @returns A {@link Command} transparently composing the two stages.
|
|
414
341
|
*
|
|
415
342
|
* @example
|
|
416
343
|
* ```ts
|
|
@@ -426,40 +353,37 @@ export function commandWithSubcommands<Context, Payload, Result>(
|
|
|
426
353
|
*/
|
|
427
354
|
export function commandChained<Context, Payload, Result>(
|
|
428
355
|
information: CommandInformation,
|
|
429
|
-
operation:
|
|
430
|
-
|
|
431
|
-
):
|
|
356
|
+
operation: Operation<Context, Payload>,
|
|
357
|
+
subcommand: Command<Payload, Result>,
|
|
358
|
+
): Command<Context, Result> {
|
|
432
359
|
return {
|
|
433
360
|
getInformation() {
|
|
434
361
|
return information;
|
|
435
362
|
},
|
|
436
|
-
|
|
363
|
+
consumeAndMakeDecoder(readerArgs: ReaderArgs) {
|
|
437
364
|
try {
|
|
438
|
-
const
|
|
439
|
-
const
|
|
365
|
+
const operationDecoder = operation.consumeAndMakeDecoder(readerArgs);
|
|
366
|
+
const subcommandDecoder = subcommand.consumeAndMakeDecoder(readerArgs);
|
|
440
367
|
return {
|
|
441
368
|
generateUsage() {
|
|
442
|
-
const
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
nextCommandUsage.positionals,
|
|
451
|
-
),
|
|
452
|
-
subcommands: nextCommandUsage.subcommands,
|
|
453
|
-
options: operationUsage.options.concat(nextCommandUsage.options),
|
|
454
|
-
};
|
|
369
|
+
const subcommandUsage = subcommandDecoder.generateUsage();
|
|
370
|
+
const currentUsage = generateUsageShallow(information, operation);
|
|
371
|
+
currentUsage.segments.push(...subcommandUsage.segments);
|
|
372
|
+
currentUsage.information = subcommandUsage.information;
|
|
373
|
+
currentUsage.positionals.push(...subcommandUsage.positionals);
|
|
374
|
+
currentUsage.subcommands = subcommandUsage.subcommands;
|
|
375
|
+
currentUsage.options.push(...subcommandUsage.options);
|
|
376
|
+
return currentUsage;
|
|
455
377
|
},
|
|
456
|
-
|
|
457
|
-
const
|
|
458
|
-
|
|
378
|
+
decodeAndMakeInterpreter() {
|
|
379
|
+
const operationInterpreter =
|
|
380
|
+
operationDecoder.decodeAndMakeInterpreter();
|
|
381
|
+
const subcommandInterpreter =
|
|
382
|
+
subcommandDecoder.decodeAndMakeInterpreter();
|
|
459
383
|
return {
|
|
460
384
|
async executeWithContext(context: Context) {
|
|
461
|
-
return await
|
|
462
|
-
await
|
|
385
|
+
return await subcommandInterpreter.executeWithContext(
|
|
386
|
+
await operationInterpreter.executeWithContext(context),
|
|
463
387
|
);
|
|
464
388
|
},
|
|
465
389
|
};
|
|
@@ -468,18 +392,11 @@ export function commandChained<Context, Payload, Result>(
|
|
|
468
392
|
} catch (error) {
|
|
469
393
|
return {
|
|
470
394
|
generateUsage() {
|
|
471
|
-
const
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
.map((positional) => breadcrumbPositional(positional.label))
|
|
475
|
-
.concat([breadcrumbPositional("[REST]...")]),
|
|
476
|
-
information: information,
|
|
477
|
-
positionals: operationUsage.positionals,
|
|
478
|
-
subcommands: [],
|
|
479
|
-
options: operationUsage.options,
|
|
480
|
-
};
|
|
395
|
+
const currentUsage = generateUsageShallow(information, operation);
|
|
396
|
+
currentUsage.segments.push(segmentPositional("[REST]..."));
|
|
397
|
+
return currentUsage;
|
|
481
398
|
},
|
|
482
|
-
|
|
399
|
+
decodeAndMakeInterpreter() {
|
|
483
400
|
throw error;
|
|
484
401
|
},
|
|
485
402
|
};
|
|
@@ -488,10 +405,26 @@ export function commandChained<Context, Payload, Result>(
|
|
|
488
405
|
};
|
|
489
406
|
}
|
|
490
407
|
|
|
491
|
-
function
|
|
408
|
+
function segmentPositional(value: string): CommandUsageSegment {
|
|
492
409
|
return { positional: value };
|
|
493
410
|
}
|
|
494
411
|
|
|
495
|
-
function
|
|
412
|
+
function segmentCommand(value: string): CommandUsageSegment {
|
|
496
413
|
return { command: value };
|
|
497
414
|
}
|
|
415
|
+
|
|
416
|
+
function generateUsageShallow(
|
|
417
|
+
information: CommandInformation,
|
|
418
|
+
operation: Operation<any, any>,
|
|
419
|
+
): CommandUsage {
|
|
420
|
+
const { positionals, options } = operation.generateUsage();
|
|
421
|
+
return {
|
|
422
|
+
segments: positionals.map((positional) =>
|
|
423
|
+
segmentPositional(positional.label),
|
|
424
|
+
),
|
|
425
|
+
information,
|
|
426
|
+
positionals,
|
|
427
|
+
subcommands: [],
|
|
428
|
+
options,
|
|
429
|
+
};
|
|
430
|
+
}
|