cli-kiss 0.2.3 → 0.2.5

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.
@@ -1,6 +1,4 @@
1
1
  import { Operation } from "./Operation";
2
- import { OptionUsage } from "./Option";
3
- import { PositionalUsage } from "./Positional";
4
2
  import { ReaderArgs } from "./Reader";
5
3
  import {
6
4
  TypoError,
@@ -9,22 +7,21 @@ import {
9
7
  typoStyleUserInput,
10
8
  TypoText,
11
9
  } from "./Typo";
10
+ import { UsageCommand } from "./Usage";
12
11
 
13
12
  /**
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.
13
+ * A CLI command. Created with {@link command}, {@link commandWithSubcommands}, or {@link commandChained}.
17
14
  *
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.
15
+ * @typeParam Context - Injected at execution; forwarded to handlers.
16
+ * @typeParam Result - Produced on execution; typically `void`.
20
17
  */
21
18
  export type Command<Context, Result> = {
22
19
  /**
23
- * Returns the command's static metadata.
20
+ * Returns static metadata.
24
21
  */
25
22
  getInformation(): CommandInformation;
26
23
  /**
27
- * Consumes args in a `readerArgs` and returns a {@link CommandDecoder}.
24
+ * Registers options/positionals on `readerArgs`; returns a {@link CommandDecoder}.
28
25
  */
29
26
  consumeAndMakeDecoder(
30
27
  readerArgs: ReaderArgs,
@@ -39,10 +36,9 @@ export type Command<Context, Result> = {
39
36
  */
40
37
  export type CommandDecoder<Context, Result> = {
41
38
  /**
42
- * Builds the {@link CommandUsage} for the current command path.
43
- * Used for `--help` and `usageOnError`.
39
+ * Returns {@link UsageCommand} for the current command path.
44
40
  */
45
- generateUsage(): CommandUsage;
41
+ generateUsage(): UsageCommand;
46
42
  /**
47
43
  * Creates a ready-to-execute {@link CommandInterpreter}.
48
44
  *
@@ -54,7 +50,7 @@ export type CommandDecoder<Context, Result> = {
54
50
  /**
55
51
  * A fully parsed, decoded and ready-to-execute command.
56
52
  *
57
- * @typeParam Context - Caller-supplied context.
53
+ * @typeParam Context - Context passed to the handler.
58
54
  * @typeParam Result - Value produced on success.
59
55
  */
60
56
  export type CommandInterpreter<Context, Result> = {
@@ -65,15 +61,15 @@ export type CommandInterpreter<Context, Result> = {
65
61
  };
66
62
 
67
63
  /**
68
- * Static metadata for a command, shown in `--help` output via {@link usageToStyledLines}.
64
+ * Command metadata shown in `--help` output.
69
65
  */
70
66
  export type CommandInformation = {
71
67
  /**
72
- * Short description shown in the usage header.
68
+ * Shown in the usage header.
73
69
  */
74
70
  description: string;
75
71
  /**
76
- * Short note shown in parentheses (e.g. `"deprecated"`, `"experimental"`).
72
+ * Shown in parentheses, e.g. `"deprecated"`, `"experimental"`.
77
73
  */
78
74
  hint?: string;
79
75
  /**
@@ -81,7 +77,7 @@ export type CommandInformation = {
81
77
  */
82
78
  details?: Array<string>;
83
79
  /**
84
- * Examples shown in the `Examples:` section of the usage output.
80
+ * Shown in the `Examples:` section.
85
81
  */
86
82
  examples?: Array<{
87
83
  /**
@@ -89,7 +85,7 @@ export type CommandInformation = {
89
85
  */
90
86
  explanation: string;
91
87
  /**
92
- * Command line args to show as an example of usage.
88
+ * Example command args.
93
89
  */
94
90
  commandArgs: Array<
95
91
  | string
@@ -97,73 +93,21 @@ export type CommandInformation = {
97
93
  | { subcommand: string }
98
94
  | {
99
95
  option:
100
- | { long: string; value?: string }
101
- | { short: string; value?: string };
96
+ | { long: string; inlined?: string; separated?: Array<string> }
97
+ | { short: string; inlined?: string; separated?: Array<string> };
102
98
  }
103
99
  >;
104
100
  }>;
105
101
  };
106
102
 
107
- /**
108
- * Full usage/help model.
109
- * Produced by {@link CommandDecoder.generateUsage},
110
- * Consumed by {@link usageToStyledLines}.
111
- */
112
- export type CommandUsage = {
113
- /**
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.
120
- */
121
- information: CommandInformation;
122
- /**
123
- * Positionals in declaration order.
124
- */
125
- positionals: Array<PositionalUsage>;
126
- /**
127
- * Available subcommands. Non-empty when subcommand was not specified.
128
- */
129
- subcommands: Array<CommandUsageSubcommand>;
130
- /**
131
- * Options in registration order.
132
- */
133
- options: Array<OptionUsage>;
134
- };
135
-
136
- /**
137
- * One element in the usage segment trail.
138
- */
139
- export type CommandUsageSegment = { positional: string } | { command: string };
140
-
141
- /**
142
- * Subcommand entry shown in the `Subcommands:` section of the usage output.
143
- */
144
- export type CommandUsageSubcommand = {
145
- /**
146
- * Literal token the user types (e.g. `"deploy"`).
147
- */
148
- name: string;
149
- /**
150
- * Short description from the subcommand's {@link CommandInformation}.
151
- */
152
- description: string | undefined;
153
- /**
154
- * Hint from the subcommand's {@link CommandInformation}.
155
- */
156
- hint: string | undefined;
157
- };
158
-
159
103
  /**
160
104
  * Creates a leaf command that directly executes an {@link Operation}.
161
105
  *
162
106
  * @typeParam Context - Context forwarded to the handler.
163
107
  * @typeParam Result - Value returned by the handler.
164
108
  *
165
- * @param information - Command metadata (description, hint, details).
166
- * @param operation - Defines: options, positionals, and the handler.
109
+ * @param information - Command metadata.
110
+ * @param operation - Options, positionals, and handler.
167
111
  * @returns A {@link Command}.
168
112
  *
169
113
  * @example
@@ -171,7 +115,7 @@ export type CommandUsageSubcommand = {
171
115
  * const greet = command(
172
116
  * { description: "Greet a user" },
173
117
  * operation(
174
- * { options: {}, positionals: [positionalRequired({ type: typeString, label: "NAME" })] },
118
+ * { options: {}, positionals: [positionalRequired({ type: type("name") })] },
175
119
  * async (_ctx, { positionals: [name] }) => console.log(`Hello, ${name}!`),
176
120
  * ),
177
121
  * );
@@ -198,7 +142,7 @@ export function command<Context, Result>(
198
142
  );
199
143
  }
200
144
  return {
201
- generateUsage: () => generateUsageShallow(information, operation),
145
+ generateUsage: () => generateUsageLeaf(information, operation),
202
146
  decodeAndMakeInterpreter() {
203
147
  const operationInterpreter =
204
148
  operationDecoder.decodeAndMakeInterpreter();
@@ -211,7 +155,7 @@ export function command<Context, Result>(
211
155
  };
212
156
  } catch (error) {
213
157
  return {
214
- generateUsage: () => generateUsageShallow(information, operation),
158
+ generateUsage: () => generateUsageLeaf(information, operation),
215
159
  decodeAndMakeInterpreter() {
216
160
  throw error;
217
161
  },
@@ -222,17 +166,16 @@ export function command<Context, Result>(
222
166
  }
223
167
 
224
168
  /**
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.
169
+ * Creates a command that runs `operation` first, then dispatches to a named subcommand.
227
170
  *
228
171
  * @typeParam Context - Context accepted by `operation`.
229
172
  * @typeParam Payload - Output of `operation`; becomes the subcommand's context.
230
173
  * @typeParam Result - Value produced by the selected subcommand.
231
174
  *
232
- * @param information - Command metadata (description, hint, details).
233
- * @param operation - Always runs first; its output becomes the subcommand's context.
175
+ * @param information - Command metadata.
176
+ * @param operation - Runs first; output becomes the subcommand's context.
234
177
  * @param subcommands - Map of subcommand names to their {@link Command}s.
235
- * @returns A {@link Command} that dispatches to one of the provided subcommands.
178
+ * @returns A dispatching {@link Command}.
236
179
  *
237
180
  * @example
238
181
  * ```ts
@@ -249,7 +192,7 @@ export function command<Context, Result>(
249
192
  export function commandWithSubcommands<Context, Payload, Result>(
250
193
  information: CommandInformation,
251
194
  operation: Operation<Context, Payload>,
252
- subcommands: { [subcommand: Lowercase<string>]: Command<Payload, Result> },
195
+ subcommands: { [subcommand: string]: Command<Payload, Result> },
253
196
  ): Command<Context, Result> {
254
197
  return {
255
198
  getInformation() {
@@ -262,17 +205,16 @@ export function commandWithSubcommands<Context, Payload, Result>(
262
205
  if (subcommandName === undefined) {
263
206
  throw new TypoError(
264
207
  new TypoText(
265
- new TypoString(`<SUBCOMMAND>`, typoStyleUserInput),
208
+ new TypoString(`<subcommand>`, typoStyleUserInput),
266
209
  new TypoString(`: Is required, but was not provided`),
267
210
  ),
268
211
  );
269
212
  }
270
- const subcommandInput =
271
- subcommands[subcommandName as Lowercase<string>];
213
+ const subcommandInput = subcommands[subcommandName];
272
214
  if (subcommandInput === undefined) {
273
215
  throw new TypoError(
274
216
  new TypoText(
275
- new TypoString(`<SUBCOMMAND>`, typoStyleUserInput),
217
+ new TypoString(`<subcommand>`, typoStyleUserInput),
276
218
  new TypoString(`: Invalid value: `),
277
219
  new TypoString(`"${subcommandName}"`, typoStyleQuote),
278
220
  ),
@@ -283,8 +225,8 @@ export function commandWithSubcommands<Context, Payload, Result>(
283
225
  return {
284
226
  generateUsage() {
285
227
  const subcommandUsage = subcommandDecoder.generateUsage();
286
- const currentUsage = generateUsageShallow(information, operation);
287
- currentUsage.segments.push(segmentCommand(subcommandName));
228
+ const currentUsage = generateUsageLeaf(information, operation);
229
+ currentUsage.segments.push({ subcommand: subcommandName });
288
230
  currentUsage.segments.push(...subcommandUsage.segments);
289
231
  currentUsage.information = subcommandUsage.information;
290
232
  currentUsage.positionals.push(...subcommandUsage.positionals);
@@ -309,8 +251,8 @@ export function commandWithSubcommands<Context, Payload, Result>(
309
251
  } catch (error) {
310
252
  return {
311
253
  generateUsage() {
312
- const currentUsage = generateUsageShallow(information, operation);
313
- currentUsage.segments.push(segmentPositional("<SUBCOMMAND>"));
254
+ const currentUsage = generateUsageLeaf(information, operation);
255
+ currentUsage.segments.push({ positional: "<subcommand>" });
314
256
  for (const [name, subcommand] of Object.entries(subcommands)) {
315
257
  const { description, hint } = subcommand.getInformation();
316
258
  currentUsage.subcommands.push({ name, description, hint });
@@ -334,22 +276,10 @@ export function commandWithSubcommands<Context, Payload, Result>(
334
276
  * @typeParam Payload - Output of `operation`; becomes `subcommand`'s context.
335
277
  * @typeParam Result - Value produced by `subcommand`.
336
278
  *
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.
341
- *
342
- * @example
343
- * ```ts
344
- * const authenticatedDeploy = commandChained(
345
- * { description: "Authenticate then deploy" },
346
- * operation(
347
- * { options: { token: optionSingleValue({ long: "token", type: typeString, default: () => "" }) }, positionals: [] },
348
- * async (_ctx, { options: { token } }) => ({ token }),
349
- * ),
350
- * command({ description: "Deploy" }, deployOperation),
351
- * );
352
- * ```
279
+ * @param information - Command metadata.
280
+ * @param operation - Runs first; output becomes `subcommand`'s context.
281
+ * @param subcommand - Runs after `operation`.
282
+ * @returns A {@link Command} composing both stages.
353
283
  */
354
284
  export function commandChained<Context, Payload, Result>(
355
285
  information: CommandInformation,
@@ -367,7 +297,7 @@ export function commandChained<Context, Payload, Result>(
367
297
  return {
368
298
  generateUsage() {
369
299
  const subcommandUsage = subcommandDecoder.generateUsage();
370
- const currentUsage = generateUsageShallow(information, operation);
300
+ const currentUsage = generateUsageLeaf(information, operation);
371
301
  currentUsage.segments.push(...subcommandUsage.segments);
372
302
  currentUsage.information = subcommandUsage.information;
373
303
  currentUsage.positionals.push(...subcommandUsage.positionals);
@@ -392,8 +322,8 @@ export function commandChained<Context, Payload, Result>(
392
322
  } catch (error) {
393
323
  return {
394
324
  generateUsage() {
395
- const currentUsage = generateUsageShallow(information, operation);
396
- currentUsage.segments.push(segmentPositional("[REST]..."));
325
+ const currentUsage = generateUsageLeaf(information, operation);
326
+ currentUsage.segments.push({ positional: "[REST]..." });
397
327
  return currentUsage;
398
328
  },
399
329
  decodeAndMakeInterpreter() {
@@ -405,23 +335,15 @@ export function commandChained<Context, Payload, Result>(
405
335
  };
406
336
  }
407
337
 
408
- function segmentPositional(value: string): CommandUsageSegment {
409
- return { positional: value };
410
- }
411
-
412
- function segmentCommand(value: string): CommandUsageSegment {
413
- return { command: value };
414
- }
415
-
416
- function generateUsageShallow(
338
+ function generateUsageLeaf(
417
339
  information: CommandInformation,
418
340
  operation: Operation<any, any>,
419
- ): CommandUsage {
341
+ ): UsageCommand {
420
342
  const { positionals, options } = operation.generateUsage();
421
343
  return {
422
- segments: positionals.map((positional) =>
423
- segmentPositional(positional.label),
424
- ),
344
+ segments: positionals.map((positional) => ({
345
+ positional: positional.label,
346
+ })),
425
347
  information,
426
348
  positionals,
427
349
  subcommands: [],
@@ -1,6 +1,7 @@
1
- import { Option, OptionDecoder, OptionUsage } from "./Option";
2
- import { Positional, PositionalDecoder, PositionalUsage } from "./Positional";
1
+ import { Option, OptionDecoder } from "./Option";
2
+ import { Positional, PositionalDecoder } from "./Positional";
3
3
  import { ReaderArgs } from "./Reader";
4
+ import { UsageOption, UsagePositional } from "./Usage";
4
5
 
5
6
  /**
6
7
  * Options, positionals, and an async handler that together form the logic of a CLI command.
@@ -8,14 +9,23 @@ import { ReaderArgs } from "./Reader";
8
9
  * Created with {@link operation} and passed to {@link command},
9
10
  * {@link commandWithSubcommands}, or {@link commandChained}.
10
11
  *
11
- * @typeParam Context - Injected at execution time; forwarded to handlers. Use to inject dependencies.
12
- * @typeParam Result - Value produced on execution; typically `void` for leaf commands.
12
+ * @typeParam Context - Injected at execution; forwarded to handlers.
13
+ * @typeParam Result - Value produced on execution; typically `void`.
13
14
  */
14
15
  export type Operation<Context, Result> = {
15
16
  /**
16
17
  * Returns usage metadata without consuming any arguments.
17
18
  */
18
- generateUsage(): OperationUsage;
19
+ generateUsage(): {
20
+ /**
21
+ * Registered options.
22
+ */
23
+ options: Array<UsageOption>;
24
+ /**
25
+ * Declared positionals, in order.
26
+ */
27
+ positionals: Array<UsagePositional>;
28
+ };
19
29
  /**
20
30
  * Consumes args from `readerArgs` and returns an {@link OperationDecoder}.
21
31
  */
@@ -52,27 +62,10 @@ export type OperationInterpreter<Context, Result> = {
52
62
  executeWithContext(context: Context): Promise<Result>;
53
63
  };
54
64
 
55
- /**
56
- * Usage metadata produced by {@link Operation.generateUsage}.
57
- * Consumed when building {@link CommandUsage}.
58
- */
59
- export type OperationUsage = {
60
- /**
61
- * Usage descriptors for all registered options.
62
- */
63
- options: Array<OptionUsage>;
64
- /**
65
- * Usage descriptors for all declared positionals, in order.
66
- */
67
- positionals: Array<PositionalUsage>;
68
- };
69
-
70
65
  /**
71
66
  * Creates an {@link Operation} from options, positionals, and an async handler.
72
67
  *
73
- * The `handler` receives the parent `context` and an `inputs` object with
74
- * `options` (keyed by the same names declared in `inputs.options`) and
75
- * `positionals` (a tuple in declaration order).
68
+ * The `handler` receives `context` and `inputs` with decoded `options` and `positionals`.
76
69
  *
77
70
  * @typeParam Context - Context type accepted by the handler.
78
71
  * @typeParam Result - Return type of the handler.
@@ -83,20 +76,20 @@ export type OperationUsage = {
83
76
  * @param inputs.options - Map of keys to {@link Option} descriptors.
84
77
  * @param inputs.positionals - Ordered array of {@link Positional} descriptors.
85
78
  * @param handler - Async function implementing the command logic.
86
- * @returns An {@link Operation} ready to be composed into a command.
79
+ * @returns An {@link Operation}.
87
80
  *
88
81
  * @example
89
82
  * ```ts
90
83
  * const greetOperation = operation(
91
84
  * {
92
85
  * options: {
93
- * loud: optionFlag({ long: "loud", description: "Print in uppercase" }),
86
+ * loud: optionFlag({ long: "loud", description: "Print in uppercase", default: false }),
94
87
  * },
95
88
  * positionals: [
96
- * positionalRequired({ type: typeString, label: "NAME", description: "Name to greet" }),
89
+ * positionalRequired({ type: type("name"), description: "Name to greet" }),
97
90
  * ],
98
91
  * },
99
- * async (_ctx, { options: { loud }, positionals: [name] }) => {
92
+ * async function (_ctx, { options: { loud }, positionals: [name] }) {
100
93
  * const message = `Hello, ${name}!`;
101
94
  * console.log(loud ? message.toUpperCase() : message);
102
95
  * },
@@ -123,14 +116,12 @@ export function operation<
123
116
  ): Operation<Context, Result> {
124
117
  return {
125
118
  generateUsage() {
126
- const optionsUsage = new Array<OptionUsage>();
119
+ const optionsUsage = new Array<UsageOption>();
127
120
  for (const optionKey in inputs.options) {
128
121
  const optionInput = inputs.options[optionKey]!;
129
- if (optionInput) {
130
- optionsUsage.push(optionInput.generateUsage());
131
- }
122
+ optionsUsage.push(optionInput.generateUsage());
132
123
  }
133
- const positionalsUsage = new Array<PositionalUsage>();
124
+ const positionalsUsage = new Array<UsagePositional>();
134
125
  for (const positionalInput of inputs.positionals) {
135
126
  positionalsUsage.push(positionalInput.generateUsage());
136
127
  }