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/Positional.ts
CHANGED
|
@@ -8,17 +8,82 @@ import {
|
|
|
8
8
|
TypoText,
|
|
9
9
|
} from "./Typo";
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Describes a single positional argument — a bare (non-option) token on the command
|
|
13
|
+
* line — together with its parsing and usage-generation logic.
|
|
14
|
+
*
|
|
15
|
+
* Positionals are created with {@link positionalRequired}, {@link positionalOptional}, or
|
|
16
|
+
* {@link positionalVariadics} and are passed via the `positionals` array of
|
|
17
|
+
* {@link operation}, where they are consumed in declaration order.
|
|
18
|
+
*
|
|
19
|
+
* @typeParam Value - The TypeScript type of the parsed positional value.
|
|
20
|
+
*/
|
|
11
21
|
export type Positional<Value> = {
|
|
22
|
+
/** Returns human-readable metadata used to render the `Positionals:` section of help. */
|
|
12
23
|
generateUsage(): PositionalUsage;
|
|
24
|
+
/**
|
|
25
|
+
* Consumes the next positional token(s) from `readerPositionals` and returns the
|
|
26
|
+
* decoded value.
|
|
27
|
+
*
|
|
28
|
+
* @param readerPositionals - The shared {@link ReaderArgs} that manages the queue of
|
|
29
|
+
* remaining positional tokens.
|
|
30
|
+
* @throws {@link TypoError} if the positional is required but absent, or if the raw
|
|
31
|
+
* value fails type decoding.
|
|
32
|
+
*/
|
|
13
33
|
consumePositionals(readerPositionals: ReaderPositionals): Value;
|
|
14
34
|
};
|
|
15
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Human-readable metadata for a single positional argument, used to render the
|
|
38
|
+
* `Positionals:` section of the help output produced by {@link usageToStyledLines}.
|
|
39
|
+
*/
|
|
16
40
|
export type PositionalUsage = {
|
|
41
|
+
/** Short description of what the positional represents. */
|
|
17
42
|
description: string | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Optional supplementary note shown in parentheses next to the description.
|
|
45
|
+
* Suitable for short caveats such as `"defaults to 'world'"`.
|
|
46
|
+
*/
|
|
18
47
|
hint: string | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* The placeholder label shown in the usage line and the `Positionals:` section.
|
|
50
|
+
* Required positionals use angle-bracket notation (e.g. `"<NAME>"`); optional ones
|
|
51
|
+
* use square-bracket notation (e.g. `"[FILE]"`); variadic ones append `...`
|
|
52
|
+
* (e.g. `"[ITEM]..."`).
|
|
53
|
+
*/
|
|
19
54
|
label: Uppercase<string>;
|
|
20
55
|
};
|
|
21
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Creates a required positional argument — one that must be present on the command line.
|
|
59
|
+
*
|
|
60
|
+
* The parser consumes the next available positional token and decodes it with
|
|
61
|
+
* `definition.type`. If no token is available, a {@link TypoError} is thrown immediately
|
|
62
|
+
* during parsing (i.e. inside {@link OperationDescriptor.createFactory}).
|
|
63
|
+
*
|
|
64
|
+
* The label displayed in the usage line defaults to the uppercased `type.content`
|
|
65
|
+
* wrapped in angle brackets (e.g. `<STRING>`). Supply `label` to override.
|
|
66
|
+
*
|
|
67
|
+
* @typeParam Value - The TypeScript type produced by the type decoder.
|
|
68
|
+
*
|
|
69
|
+
* @param definition - Configuration for the positional.
|
|
70
|
+
* @param definition.description - Human-readable description for the help output.
|
|
71
|
+
* @param definition.hint - Optional supplementary note shown in parentheses.
|
|
72
|
+
* @param definition.label - Custom label shown in the usage line (without angle brackets).
|
|
73
|
+
* Defaults to the uppercased `type.content`.
|
|
74
|
+
* @param definition.type - The {@link Type} used to decode the raw string token.
|
|
75
|
+
* @returns A {@link Positional}`<Value>`.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```ts
|
|
79
|
+
* const namePositional = positionalRequired({
|
|
80
|
+
* type: typeString,
|
|
81
|
+
* label: "NAME",
|
|
82
|
+
* description: "The name to greet",
|
|
83
|
+
* });
|
|
84
|
+
* // Parses: my-cli Alice → "Alice"
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
22
87
|
export function positionalRequired<Value>(definition: {
|
|
23
88
|
description?: string;
|
|
24
89
|
hint?: string;
|
|
@@ -49,6 +114,41 @@ export function positionalRequired<Value>(definition: {
|
|
|
49
114
|
};
|
|
50
115
|
}
|
|
51
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Creates an optional positional argument — one that may or may not appear on the
|
|
119
|
+
* command line.
|
|
120
|
+
*
|
|
121
|
+
* The parser consumes the next available positional token. If no token is available,
|
|
122
|
+
* `definition.default()` is called to supply the fallback value. If the default factory
|
|
123
|
+
* throws, a {@link TypoError} is produced.
|
|
124
|
+
*
|
|
125
|
+
* The label displayed in the usage line defaults to the uppercased `type.content`
|
|
126
|
+
* wrapped in square brackets (e.g. `[STRING]`). Supply `label` to override.
|
|
127
|
+
*
|
|
128
|
+
* @typeParam Value - The TypeScript type produced by the type decoder (or the default).
|
|
129
|
+
*
|
|
130
|
+
* @param definition - Configuration for the positional.
|
|
131
|
+
* @param definition.description - Human-readable description for the help output.
|
|
132
|
+
* @param definition.hint - Optional supplementary note shown in parentheses.
|
|
133
|
+
* @param definition.label - Custom label shown in the usage line (without square brackets).
|
|
134
|
+
* Defaults to the uppercased `type.content`.
|
|
135
|
+
* @param definition.type - The {@link Type} used to decode the raw string token.
|
|
136
|
+
* @param definition.default - Factory called when the positional is absent to supply the
|
|
137
|
+
* default value. Throw from this factory to make omission an error.
|
|
138
|
+
* @returns A {@link Positional}`<Value>`.
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* ```ts
|
|
142
|
+
* const greeteePositional = positionalOptional({
|
|
143
|
+
* type: typeString,
|
|
144
|
+
* label: "NAME",
|
|
145
|
+
* description: "Name to greet (default: world)",
|
|
146
|
+
* default: () => "world",
|
|
147
|
+
* });
|
|
148
|
+
* // my-cli → "world"
|
|
149
|
+
* // my-cli Alice → "Alice"
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
52
152
|
export function positionalOptional<Value>(definition: {
|
|
53
153
|
description?: string;
|
|
54
154
|
hint?: string;
|
|
@@ -85,6 +185,45 @@ export function positionalOptional<Value>(definition: {
|
|
|
85
185
|
};
|
|
86
186
|
}
|
|
87
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Creates a variadic positional argument — one that collects zero or more remaining
|
|
190
|
+
* positional tokens into an array.
|
|
191
|
+
*
|
|
192
|
+
* The parser greedily consumes tokens until either there are no more tokens or it
|
|
193
|
+
* encounters the optional `endDelimiter` sentinel string, which is consumed but not
|
|
194
|
+
* included in the result. Each token is decoded independently with `definition.type`.
|
|
195
|
+
*
|
|
196
|
+
* If absent entirely, the result is an empty array `[]`.
|
|
197
|
+
*
|
|
198
|
+
* The label displayed in the usage line defaults to the uppercased `type.content`
|
|
199
|
+
* wrapped in square brackets followed by `...` (e.g. `[STRING]...`). When an
|
|
200
|
+
* `endDelimiter` is configured, the delimiter is also shown (e.g. `[STRING]...["--"]`).
|
|
201
|
+
* Supply `label` to override the base label.
|
|
202
|
+
*
|
|
203
|
+
* @typeParam Value - The TypeScript type produced by the type decoder for each token.
|
|
204
|
+
*
|
|
205
|
+
* @param definition - Configuration for the variadic positional.
|
|
206
|
+
* @param definition.endDelimiter - Optional sentinel string that signals the end of
|
|
207
|
+
* the variadic sequence (e.g. `"--"`). When encountered it is consumed but not
|
|
208
|
+
* included in the result array.
|
|
209
|
+
* @param definition.description - Human-readable description for the help output.
|
|
210
|
+
* @param definition.hint - Optional supplementary note shown in parentheses.
|
|
211
|
+
* @param definition.label - Custom label shown in the usage line (without brackets).
|
|
212
|
+
* Defaults to the uppercased `type.content`.
|
|
213
|
+
* @param definition.type - The {@link Type} used to decode each raw string token.
|
|
214
|
+
* @returns A {@link Positional}`<Array<Value>>`.
|
|
215
|
+
*
|
|
216
|
+
* @example
|
|
217
|
+
* ```ts
|
|
218
|
+
* const filesPositional = positionalVariadics({
|
|
219
|
+
* type: typeString,
|
|
220
|
+
* label: "FILE",
|
|
221
|
+
* description: "Files to process",
|
|
222
|
+
* });
|
|
223
|
+
* // my-cli a.ts b.ts c.ts → ["a.ts", "b.ts", "c.ts"]
|
|
224
|
+
* // my-cli → []
|
|
225
|
+
* ```
|
|
226
|
+
*/
|
|
88
227
|
export function positionalVariadics<Value>(definition: {
|
|
89
228
|
endDelimiter?: string;
|
|
90
229
|
description?: string;
|
package/src/lib/Reader.ts
CHANGED
|
@@ -6,23 +6,88 @@ import {
|
|
|
6
6
|
TypoText,
|
|
7
7
|
} from "./Typo";
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* An opaque key that uniquely identifies a registered CLI option within a
|
|
11
|
+
* {@link ReaderArgs} instance.
|
|
12
|
+
*
|
|
13
|
+
* Keys are returned by {@link ReaderArgs.registerOption} and passed back to
|
|
14
|
+
* {@link ReaderArgs.getOptionValues} to retrieve the parsed values. The internal
|
|
15
|
+
* representation is intentionally opaque — treat it as a handle, not a string.
|
|
16
|
+
*/
|
|
9
17
|
export type ReaderOptionKey = (string | { __brand: "ReaderOptionKey" }) & {
|
|
10
18
|
__brand: "ReaderOptionKey";
|
|
11
19
|
};
|
|
12
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Interface for registering and querying CLI options during argument parsing.
|
|
23
|
+
*
|
|
24
|
+
* {@link ReaderArgs} implements both `ReaderOptions` and {@link ReaderPositionals}.
|
|
25
|
+
* The two interfaces are exposed separately so that option and positional parsing logic
|
|
26
|
+
* can depend only on the capability they need.
|
|
27
|
+
*/
|
|
13
28
|
export type ReaderOptions = {
|
|
29
|
+
/**
|
|
30
|
+
* Registers a new option so the parser can recognise it when scanning argument tokens.
|
|
31
|
+
*
|
|
32
|
+
* @param definition.longs - The long-form names (without `--`) for this option.
|
|
33
|
+
* @param definition.shorts - The short-form names (without `-`) for this option.
|
|
34
|
+
* @param definition.valued - When `true`, the option consumes the following token as
|
|
35
|
+
* its value. When `false`, the option is a boolean flag.
|
|
36
|
+
* @returns An opaque {@link ReaderOptionKey} used to retrieve parsed values later.
|
|
37
|
+
* @throws `Error` if any of the given names has already been registered, or if a
|
|
38
|
+
* short name overlaps (is a prefix of, or has as a prefix, another registered short).
|
|
39
|
+
*/
|
|
14
40
|
registerOption(definition: {
|
|
15
41
|
longs: Array<string>;
|
|
16
42
|
shorts: Array<string>;
|
|
17
43
|
valued: boolean;
|
|
18
44
|
}): ReaderOptionKey;
|
|
45
|
+
/**
|
|
46
|
+
* Returns all values collected for the option identified by `key`.
|
|
47
|
+
*
|
|
48
|
+
* @param key - The key returned by a prior {@link ReaderOptions.registerOption} call.
|
|
49
|
+
* @returns An array of raw string values, one per occurrence of the option on the
|
|
50
|
+
* command line. Empty if the option was never provided.
|
|
51
|
+
* @throws `Error` if `key` was not previously registered on this instance.
|
|
52
|
+
*/
|
|
19
53
|
getOptionValues(key: ReaderOptionKey): Array<string>;
|
|
20
54
|
};
|
|
21
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Interface for consuming positional (non-option) argument tokens during parsing.
|
|
58
|
+
*
|
|
59
|
+
* {@link ReaderArgs} implements both {@link ReaderOptions} and `ReaderPositionals`.
|
|
60
|
+
*/
|
|
22
61
|
export type ReaderPositionals = {
|
|
62
|
+
/**
|
|
63
|
+
* Consumes and returns the next positional token from the argument list, skipping
|
|
64
|
+
* any option tokens (which are parsed as side-effects).
|
|
65
|
+
*
|
|
66
|
+
* @returns The next positional string value, or `undefined` if no more positionals
|
|
67
|
+
* are available.
|
|
68
|
+
* @throws {@link TypoError} if an unrecognised option token is encountered while
|
|
69
|
+
* scanning for the next positional.
|
|
70
|
+
*/
|
|
23
71
|
consumePositional(): string | undefined;
|
|
24
72
|
};
|
|
25
73
|
|
|
74
|
+
/**
|
|
75
|
+
* The core argument parser for `cli-kiss`. Parses a flat array of raw CLI tokens into
|
|
76
|
+
* named options and positional values.
|
|
77
|
+
*
|
|
78
|
+
* Options must be registered with {@link ReaderArgs.registerOption} **before**
|
|
79
|
+
* {@link ReaderArgs.consumePositional} is called, because the parser needs to know
|
|
80
|
+
* whether each token is an option name, an option value, or a bare positional.
|
|
81
|
+
*
|
|
82
|
+
* **Supported argument syntax:**
|
|
83
|
+
* - Long options: `--name`, `--name value`, `--name=value`
|
|
84
|
+
* - Short options: `-n`, `-n value`, `-n=value`, `-nvalue`, `-abc` (stacked flags)
|
|
85
|
+
* - End-of-options separator: `--` — all subsequent tokens are treated as positionals.
|
|
86
|
+
*
|
|
87
|
+
* In most cases you do not need to use `ReaderArgs` directly; it is created internally
|
|
88
|
+
* by {@link runAndExit}. It is exposed for advanced use cases such as building
|
|
89
|
+
* custom runners.
|
|
90
|
+
*/
|
|
26
91
|
export class ReaderArgs {
|
|
27
92
|
#args: ReadonlyArray<string>;
|
|
28
93
|
#parsedIndex: number;
|
|
@@ -32,6 +97,10 @@ export class ReaderArgs {
|
|
|
32
97
|
#valuedByKey: Map<ReaderOptionKey, boolean>;
|
|
33
98
|
#resultByKey: Map<ReaderOptionKey, Array<string>>;
|
|
34
99
|
|
|
100
|
+
/**
|
|
101
|
+
* @param args - The raw command-line tokens to parse. Typically `process.argv.slice(2)`.
|
|
102
|
+
* The array is not modified; a read cursor is maintained internally.
|
|
103
|
+
*/
|
|
35
104
|
constructor(args: ReadonlyArray<string>) {
|
|
36
105
|
this.#args = args;
|
|
37
106
|
this.#parsedIndex = 0;
|
|
@@ -42,6 +111,24 @@ export class ReaderArgs {
|
|
|
42
111
|
this.#resultByKey = new Map();
|
|
43
112
|
}
|
|
44
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Registers a CLI option so the parser can recognise it.
|
|
116
|
+
*
|
|
117
|
+
* All `longs` and `shorts` are associated with the same returned key. Calling
|
|
118
|
+
* `getOptionValues(key)` after parsing will return values collected under any of the
|
|
119
|
+
* registered names.
|
|
120
|
+
*
|
|
121
|
+
* Short names support stacking (e.g. `-abc` is parsed as `-a -b -c`) and inline
|
|
122
|
+
* values (e.g. `-nvalue`). Short names must not be a prefix of, nor have as a prefix,
|
|
123
|
+
* any other registered short name — the parser uses prefix matching to parse stacked
|
|
124
|
+
* shorts, so overlapping prefixes would be ambiguous.
|
|
125
|
+
*
|
|
126
|
+
* @param definition.longs - Long-form names (without `--`).
|
|
127
|
+
* @param definition.shorts - Short-form names (without `-`).
|
|
128
|
+
* @param definition.valued - `true` if the option consumes a value; `false` for flags.
|
|
129
|
+
* @returns An opaque {@link ReaderOptionKey} to pass to {@link ReaderArgs.getOptionValues}.
|
|
130
|
+
* @throws `Error` if any name is already registered or if two short names overlap.
|
|
131
|
+
*/
|
|
45
132
|
registerOption(definition: {
|
|
46
133
|
longs: Array<string>;
|
|
47
134
|
shorts: Array<string>;
|
|
@@ -83,6 +170,15 @@ export class ReaderArgs {
|
|
|
83
170
|
return key;
|
|
84
171
|
}
|
|
85
172
|
|
|
173
|
+
/**
|
|
174
|
+
* Returns all raw string values collected for the given option key.
|
|
175
|
+
*
|
|
176
|
+
* @param key - A key previously returned by {@link ReaderArgs.registerOption}.
|
|
177
|
+
* @returns An array of string values, one per occurrence on the command line. For
|
|
178
|
+
* flags this will be `["true"]` per occurrence; for valued options it will be the
|
|
179
|
+
* literal value strings.
|
|
180
|
+
* @throws `Error` if `key` was not registered on this instance.
|
|
181
|
+
*/
|
|
86
182
|
getOptionValues(key: ReaderOptionKey): Array<string> {
|
|
87
183
|
const optionResult = this.#resultByKey.get(key);
|
|
88
184
|
if (optionResult === undefined) {
|
|
@@ -91,6 +187,20 @@ export class ReaderArgs {
|
|
|
91
187
|
return optionResult;
|
|
92
188
|
}
|
|
93
189
|
|
|
190
|
+
/**
|
|
191
|
+
* Scans forward through the argument list and returns the next bare positional token,
|
|
192
|
+
* consuming and parsing any intervening option tokens as side-effects.
|
|
193
|
+
*
|
|
194
|
+
* Option tokens encountered during the scan are recorded in the internal results map
|
|
195
|
+
* (equivalent to recording their values against their key). Any unrecognised option token
|
|
196
|
+
* causes a {@link TypoError} to be thrown immediately.
|
|
197
|
+
*
|
|
198
|
+
* After `--` is encountered, all remaining tokens are treated as positionals.
|
|
199
|
+
*
|
|
200
|
+
* @returns The next positional string, or `undefined` when the argument list is
|
|
201
|
+
* exhausted.
|
|
202
|
+
* @throws {@link TypoError} if an unrecognised option (long or short) is encountered.
|
|
203
|
+
*/
|
|
94
204
|
consumePositional(): string | undefined {
|
|
95
205
|
while (true) {
|
|
96
206
|
const arg = this.#consumeArg();
|
package/src/lib/Run.ts
CHANGED
|
@@ -3,7 +3,75 @@ import { ReaderArgs } from "./Reader";
|
|
|
3
3
|
import { TypoSupport } from "./Typo";
|
|
4
4
|
import { usageToStyledLines } from "./Usage";
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Parses the provided CLI arguments against the given command descriptor, executes
|
|
8
|
+
* the matched command, and exits the process with an appropriate exit code.
|
|
9
|
+
*
|
|
10
|
+
* This is the primary entry point for running a `cli-kiss`-based CLI application.
|
|
11
|
+
* It handles argument parsing, `--help` / `--version` flags, usage printing on errors,
|
|
12
|
+
* and exit code management.
|
|
13
|
+
*
|
|
14
|
+
* **Exit codes:**
|
|
15
|
+
* - `0` — Command executed successfully, or `--help` / `--version` was handled.
|
|
16
|
+
* - `1` — Argument parsing failed (a usage summary is also printed to stderr), or the
|
|
17
|
+
* command threw an unhandled execution error.
|
|
18
|
+
*
|
|
19
|
+
* **Built-in flags (opt-out):**
|
|
20
|
+
* - `--help` — Enabled by default (`usageOnHelp: true`). Prints the usage summary to
|
|
21
|
+
* stdout and exits with code `0`. This flag takes precedence over `--version`.
|
|
22
|
+
* - `--version` — Enabled when `buildVersion` is provided. Prints `<cliName> <version>`
|
|
23
|
+
* to stdout and exits with code `0`.
|
|
24
|
+
*
|
|
25
|
+
* @typeParam Context - Arbitrary value passed unchanged to the command's execution handler.
|
|
26
|
+
* Use this to inject dependencies (e.g. a database connection, a logger) into your commands.
|
|
27
|
+
*
|
|
28
|
+
* @param cliName - The name of the CLI program (e.g. `"my-cli"`). Used in the usage
|
|
29
|
+
* summary header and in the `--version` output.
|
|
30
|
+
* @param cliArgs - The raw command-line arguments to parse, typically `process.argv.slice(2)`.
|
|
31
|
+
* @param context - The context value forwarded to the command's execution handler.
|
|
32
|
+
* @param command - The root {@link CommandDescriptor} that describes how to parse and execute
|
|
33
|
+
* the CLI.
|
|
34
|
+
* @param options - Optional configuration for the runner.
|
|
35
|
+
* @param options.useTtyColors - Controls terminal color output in styled messages.
|
|
36
|
+
* - `true` — Always apply ANSI color codes.
|
|
37
|
+
* - `false` — Never apply color codes (plain text).
|
|
38
|
+
* - `"mock"` — Use a deterministic mock style useful for snapshot testing.
|
|
39
|
+
* - `undefined` (default) — Auto-detect based on `process.stdout.isTTY` and the
|
|
40
|
+
* `FORCE_COLOR` / `NO_COLOR` environment variables.
|
|
41
|
+
* @param options.usageOnHelp - When `true` (default), registers a `--help` flag that
|
|
42
|
+
* prints the usage summary and exits with code `0`.
|
|
43
|
+
* @param options.usageOnError - When `true` (default), prints the usage summary to
|
|
44
|
+
* stderr before the error message whenever argument parsing fails.
|
|
45
|
+
* @param options.buildVersion - When provided, registers a `--version` flag that prints
|
|
46
|
+
* `<cliName> <buildVersion>` to stdout and exits with code `0`.
|
|
47
|
+
* @param options.onError - Custom handler for errors thrown during command execution.
|
|
48
|
+
* If omitted, the error is printed to stderr via {@link TypoSupport}.
|
|
49
|
+
* @param options.onExit - Overrides the process exit function (default: `process.exit`).
|
|
50
|
+
* Useful for testing — supply a function that throws or captures the exit code instead
|
|
51
|
+
* of actually terminating the process.
|
|
52
|
+
*
|
|
53
|
+
* @returns A `Promise<never>` because the function always terminates by calling `onExit`.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```ts
|
|
57
|
+
* import { runAndExit, command, operation, positionalRequired, typeString } from "cli-kiss";
|
|
58
|
+
*
|
|
59
|
+
* const greetCommand = command(
|
|
60
|
+
* { description: "Greet someone" },
|
|
61
|
+
* operation(
|
|
62
|
+
* { options: {}, positionals: [positionalRequired({ type: typeString, label: "NAME" })] },
|
|
63
|
+
* async (_ctx, { positionals: [name] }) => {
|
|
64
|
+
* console.log(`Hello, ${name}!`);
|
|
65
|
+
* },
|
|
66
|
+
* ),
|
|
67
|
+
* );
|
|
68
|
+
*
|
|
69
|
+
* await runAndExit("greet", process.argv.slice(2), undefined, greetCommand, {
|
|
70
|
+
* buildVersion: "1.0.0",
|
|
71
|
+
* });
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
export async function runAndExit<Context>(
|
|
7
75
|
cliName: Lowercase<string>,
|
|
8
76
|
cliArgs: ReadonlyArray<string>,
|
|
9
77
|
context: Context,
|
|
@@ -13,9 +81,7 @@ export async function runAsCliAndExit<Context>(
|
|
|
13
81
|
usageOnHelp?: boolean | undefined;
|
|
14
82
|
usageOnError?: boolean | undefined;
|
|
15
83
|
buildVersion?: string | undefined;
|
|
16
|
-
|
|
17
|
-
onLogStdOut?: ((message: string) => void) | undefined;
|
|
18
|
-
onLogStdErr?: ((message: string) => void) | undefined;
|
|
84
|
+
onError?: ((error: unknown) => void) | undefined;
|
|
19
85
|
onExit?: ((code: number) => never) | undefined;
|
|
20
86
|
},
|
|
21
87
|
): Promise<never> {
|
|
@@ -44,17 +110,6 @@ export async function runAsCliAndExit<Context>(
|
|
|
44
110
|
longs: ["completion"],
|
|
45
111
|
});
|
|
46
112
|
*/
|
|
47
|
-
const typoSupport =
|
|
48
|
-
options?.useTtyColors === undefined
|
|
49
|
-
? TypoSupport.inferFromProcess()
|
|
50
|
-
: options.useTtyColors === "mock"
|
|
51
|
-
? TypoSupport.mock()
|
|
52
|
-
: options.useTtyColors
|
|
53
|
-
? TypoSupport.tty()
|
|
54
|
-
: TypoSupport.none();
|
|
55
|
-
const onLogStdOut = options?.onLogStdOut ?? console.log;
|
|
56
|
-
const onLogStdErr = options?.onLogStdErr ?? console.error;
|
|
57
|
-
const onExit = options?.onExit ?? process.exit;
|
|
58
113
|
const commandFactory = command.createFactory(readerArgs);
|
|
59
114
|
while (true) {
|
|
60
115
|
try {
|
|
@@ -64,15 +119,24 @@ export async function runAsCliAndExit<Context>(
|
|
|
64
119
|
}
|
|
65
120
|
} catch (_) {}
|
|
66
121
|
}
|
|
122
|
+
const onExit = options?.onExit ?? process.exit;
|
|
123
|
+
const typoSupport =
|
|
124
|
+
options?.useTtyColors === undefined
|
|
125
|
+
? TypoSupport.inferFromProcess()
|
|
126
|
+
: options.useTtyColors === "mock"
|
|
127
|
+
? TypoSupport.mock()
|
|
128
|
+
: options.useTtyColors
|
|
129
|
+
? TypoSupport.tty()
|
|
130
|
+
: TypoSupport.none();
|
|
67
131
|
if (usageOnHelp) {
|
|
68
132
|
if (readerArgs.getOptionValues("--help" as any).length > 0) {
|
|
69
|
-
|
|
133
|
+
console.log(computeUsageString(cliName, commandFactory, typoSupport));
|
|
70
134
|
return onExit(0);
|
|
71
135
|
}
|
|
72
136
|
}
|
|
73
137
|
if (buildVersion) {
|
|
74
138
|
if (readerArgs.getOptionValues("--version" as any).length > 0) {
|
|
75
|
-
|
|
139
|
+
console.log([cliName, buildVersion].join(" "));
|
|
76
140
|
return onExit(0);
|
|
77
141
|
}
|
|
78
142
|
}
|
|
@@ -82,18 +146,18 @@ export async function runAsCliAndExit<Context>(
|
|
|
82
146
|
await commandInstance.executeWithContext(context);
|
|
83
147
|
return onExit(0);
|
|
84
148
|
} catch (executionError) {
|
|
85
|
-
if (options?.
|
|
86
|
-
options.
|
|
149
|
+
if (options?.onError) {
|
|
150
|
+
options.onError(executionError);
|
|
87
151
|
} else {
|
|
88
|
-
|
|
152
|
+
console.error(typoSupport.computeStyledErrorMessage(executionError));
|
|
89
153
|
}
|
|
90
154
|
return onExit(1);
|
|
91
155
|
}
|
|
92
156
|
} catch (parsingError) {
|
|
93
157
|
if (options?.usageOnError ?? true) {
|
|
94
|
-
|
|
158
|
+
console.error(computeUsageString(cliName, commandFactory, typoSupport));
|
|
95
159
|
}
|
|
96
|
-
|
|
160
|
+
console.error(typoSupport.computeStyledErrorMessage(parsingError));
|
|
97
161
|
return onExit(1);
|
|
98
162
|
}
|
|
99
163
|
}
|