cli-kiss 0.1.9 → 0.2.1

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.
@@ -0,0 +1,161 @@
1
+ # Running your CLI
2
+
3
+ ## `runAndExit`
4
+
5
+ `runAndExit` is the entry point for every `cli-kiss` CLI. It parses arguments,
6
+ runs the matched command, and exits the process.
7
+
8
+ ```ts
9
+ await runAndExit(cliName, cliArgs, context, command, options?);
10
+ ```
11
+
12
+ | Parameter | Type | Description |
13
+ | --------- | ---------------------------------- | ------------------------------------------------- |
14
+ | `cliName` | `Lowercase<string>` | Program name used in help and `--version` output |
15
+ | `cliArgs` | `ReadonlyArray<string>` | Raw arguments — typically `process.argv.slice(2)` |
16
+ | `context` | `Context` | Value forwarded to every command handler |
17
+ | `command` | `CommandDescriptor<Context, void>` | The root command |
18
+ | `options` | `object?` | See below |
19
+
20
+ ### Options
21
+
22
+ | Option | Type | Default | Description |
23
+ | -------------- | -------------------------- | -------------- | ----------------------------------------------------------- |
24
+ | `buildVersion` | `string?` | — | Enables `--version` flag; prints `<cliName> <buildVersion>` |
25
+ | `usageOnHelp` | `boolean?` | `true` | Enables `--help` flag |
26
+ | `usageOnError` | `boolean?` | `true` | Prints usage to stderr when parsing fails |
27
+ | `useTtyColors` | `boolean \| "mock"?` | auto | Controls ANSI color output |
28
+ | `onError` | `(error: unknown) => void` | — | Custom handler for execution errors |
29
+ | `onExit` | `(code: number) => never` | `process.exit` | Override for testing |
30
+
31
+ ### Exit codes
32
+
33
+ | Code | Reason |
34
+ | ---- | --------------------------------------- |
35
+ | `0` | Success, `--help`, or `--version` |
36
+ | `1` | Parse error or uncaught execution error |
37
+
38
+ ## Full example
39
+
40
+ ```ts
41
+ import {
42
+ command,
43
+ commandWithSubcommands,
44
+ operation,
45
+ optionFlag,
46
+ optionSingleValue,
47
+ positionalRequired,
48
+ runAndExit,
49
+ typeString,
50
+ typeUrl,
51
+ } from "cli-kiss";
52
+
53
+ type Ctx = { db: string };
54
+
55
+ const deployCmd = command(
56
+ { description: "Deploy to production" },
57
+ operation(
58
+ {
59
+ options: {
60
+ dryRun: optionFlag({ long: "dry-run", description: "Simulate only" }),
61
+ },
62
+ positionals: [],
63
+ },
64
+ async ({ db }, { options: { dryRun } }) => {
65
+ if (dryRun) {
66
+ console.log(`[dry-run] would deploy with DB: ${db}`);
67
+ } else {
68
+ console.log(`Deploying with DB: ${db}`);
69
+ }
70
+ },
71
+ ),
72
+ );
73
+
74
+ const rootCmd = commandWithSubcommands(
75
+ { description: "My deployment CLI" },
76
+ operation(
77
+ {
78
+ options: {
79
+ dbUrl: optionSingleValue({
80
+ long: "db",
81
+ type: typeUrl,
82
+ description: "Database URL",
83
+ default: () => new URL("postgres://localhost/mydb"),
84
+ }),
85
+ },
86
+ positionals: [],
87
+ },
88
+ async (_ctx, { options: { dbUrl } }): Promise<Ctx> => ({
89
+ db: dbUrl.toString(),
90
+ }),
91
+ ),
92
+ { deploy: deployCmd },
93
+ );
94
+
95
+ await runAndExit("my-cli", process.argv.slice(2), undefined, rootCmd, {
96
+ buildVersion: "2.0.0",
97
+ });
98
+ ```
99
+
100
+ Check it
101
+
102
+ ```sh
103
+ $ my-cli --help
104
+ ```
105
+
106
+ ```text
107
+ Usage: my-cli <SUBCOMMAND>
108
+
109
+ My deployment CLI
110
+
111
+ Subcommands:
112
+ deploy Deploy to production
113
+
114
+ Options:
115
+ --db <URL> Database URL
116
+ ```
117
+
118
+ Try it
119
+
120
+ ```sh
121
+ $ my-cli deploy --dry-run
122
+ ```
123
+
124
+ ```text
125
+ [dry-run] would deploy with DB: postgres://localhost/mydb
126
+ ```
127
+
128
+ ## Color control
129
+
130
+ By default colors are auto-detected from the terminal. You can override:
131
+
132
+ ```ts
133
+ // Force colors on
134
+ await runAndExit("my-cli", args, ctx, cmd, { useTtyColors: true });
135
+
136
+ // Force colors off (useful in CI)
137
+ await runAndExit("my-cli", args, ctx, cmd, { useTtyColors: false });
138
+
139
+ // Deterministic mock output (useful in snapshot tests)
140
+ await runAndExit("my-cli", args, ctx, cmd, { useTtyColors: "mock" });
141
+ ```
142
+
143
+ ## Testing your CLI
144
+
145
+ Override `onExit` so that `runAndExit` does not terminate your test process:
146
+
147
+ ```ts
148
+ import { runAndExit } from "cli-kiss";
149
+
150
+ const exitCodes: number[] = [];
151
+
152
+ await runAndExit("my-cli", ["--help"], undefined, myCommand, {
153
+ useTtyColors: false,
154
+ onExit: (code) => {
155
+ exitCodes.push(code);
156
+ return undefined as never;
157
+ },
158
+ });
159
+
160
+ console.assert(exitCodes[0] === 0, "expected exit 0 for --help");
161
+ ```
package/docs/index.md ADDED
@@ -0,0 +1,30 @@
1
+ ---
2
+ layout: home
3
+
4
+ hero:
5
+ name: cli-kiss 💋
6
+ text: CLI for TypeScript.
7
+ tagline:
8
+ No bloat, no dependency. Only what you need.</br>Standard expected behaviour
9
+ that users are used to.</br><span>K</span>eep <span>I</span>t
10
+ <span>S</span>imple and <span>S</span>tupid, it just does the job.
11
+
12
+ actions:
13
+ - theme: brand
14
+ text: Get Started
15
+ link: /guide/01_getting_started
16
+ - theme: alt
17
+ text: View on GitHub
18
+ link: https://github.com/crypto-vincent/cli-kiss
19
+
20
+ features:
21
+ - title: Zero dependencies
22
+ details:
23
+ Ships with no runtime dependencies.<br/>Pure TypeScript, 5kb bundled.
24
+ - title: Fully typed
25
+ details:
26
+ TypeScript first.<br/>Options and positionals inputs strongly typed.
27
+ - title: Composable
28
+ details:
29
+ Easily create nested subcommands.<br/>Build complex CLIs by nesting logic.
30
+ ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cli-kiss",
3
- "version": "0.1.9",
3
+ "version": "0.2.1",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "devDependencies": {
@@ -12,12 +12,15 @@
12
12
  "ts-jest": "^29.4.4",
13
13
  "ts-node": "^10.9.2",
14
14
  "tsup": "^8.5.0",
15
- "typescript": "^5.8.3"
15
+ "typescript": "^5.8.3",
16
+ "vitepress": "^1.6.4"
16
17
  },
17
18
  "scripts": {
18
19
  "format": "prettier --write src && prettier --write tests",
19
20
  "checks": "tsc --noEmit --skipLibCheck && prettier --check src && prettier --check tests",
20
21
  "tests": "jest --config jest.config.ts",
21
- "build": "tsup src/index.ts --dts --clean --minify --sourcemap --out-dir dist"
22
+ "build": "tsup src/index.ts --dts --clean --minify --sourcemap --out-dir dist",
23
+ "docs:dev": "vitepress dev docs",
24
+ "docs:build": "vitepress build docs"
22
25
  }
23
26
  }
package/src/index.ts CHANGED
@@ -8,5 +8,4 @@ export * from "./lib/Type";
8
8
  export * from "./lib/Typo";
9
9
  export * from "./lib/Usage";
10
10
 
11
- // TODO - add documentation and examples
12
11
  // TODO - maybe add a parsed option recap on error
@@ -16,12 +16,12 @@ import {
16
16
  *
17
17
  * A `CommandDescriptor` is the central building block of a `cli-kiss` CLI. You create
18
18
  * one with {@link command}, {@link commandWithSubcommands}, or {@link commandChained},
19
- * and pass it to {@link runAsCliAndExit} to run your CLI.
19
+ * and pass it to {@link runAndExit} to run your CLI.
20
20
  *
21
21
  * @typeParam Context - The value passed into the command when it is executed. It flows
22
- * from {@link runAsCliAndExit}'s `context` argument down through the command chain.
22
+ * from {@link runAndExit}'s `context` argument down through the command chain.
23
23
  * @typeParam Result - The value produced by executing the command. For root commands
24
- * passed to {@link runAsCliAndExit} this is always `void`.
24
+ * passed to {@link runAndExit} this is always `void`.
25
25
  */
26
26
  export type CommandDescriptor<Context, Result> = {
27
27
  /** Returns the static metadata (description, hint, details) for this command. */
@@ -74,7 +74,7 @@ export type CommandInstance<Context, Result> = {
74
74
  /**
75
75
  * Executes the command with the provided context.
76
76
  *
77
- * @param context - Arbitrary value injected by the caller (see {@link runAsCliAndExit}).
77
+ * @param context - Arbitrary value injected by the caller (see {@link runAndExit}).
78
78
  * @returns A promise that resolves to the command's result, or rejects if the
79
79
  * command handler throws.
80
80
  */
@@ -84,8 +84,7 @@ export type CommandInstance<Context, Result> = {
84
84
  /**
85
85
  * Static, human-readable metadata attached to a command.
86
86
  *
87
- * This information is displayed in the usage/help output produced by
88
- * {@link usageToStyledLines}.
87
+ * This information is displayed in the usage/help output produced by {@link usageToStyledLines}.
89
88
  */
90
89
  export type CommandInformation = {
91
90
  /** Short description of what the command does. Shown prominently in the usage header. */
@@ -176,7 +175,7 @@ export type CommandUsageSubcommand = {
176
175
  * @param information - Static metadata (description, hint, details) for the command.
177
176
  * @param operation - The operation that defines options, positionals, and the execution
178
177
  * handler for this command.
179
- * @returns A {@link CommandDescriptor} suitable for passing to {@link runAsCliAndExit}
178
+ * @returns A {@link CommandDescriptor} suitable for passing to {@link runAndExit}
180
179
  * or composing with {@link commandWithSubcommands} / {@link commandChained}.
181
180
  *
182
181
  * @example
@@ -66,7 +66,7 @@ export type OperationInstance<Input, Output> = {
66
66
  * option/positional values.
67
67
  *
68
68
  * @param input - Context from the parent command (or the root context supplied to
69
- * {@link runAsCliAndExit}).
69
+ * {@link runAndExit}).
70
70
  * @returns A promise resolving to the handler's return value.
71
71
  */
72
72
  executeWithContext(input: Input): Promise<Output>;
@@ -89,7 +89,7 @@ export type OperationUsage = {
89
89
  *
90
90
  * The `handler` receives:
91
91
  * - `context` — the value passed down from the parent command (or from
92
- * {@link runAsCliAndExit}).
92
+ * {@link runAndExit}).
93
93
  * - `inputs.options` — an object whose keys match those declared in `inputs.options` and whose values are
94
94
  * the parsed option values.
95
95
  * - `inputs.positionals` — a tuple whose elements match `inputs.positionals` and whose
package/src/lib/Reader.ts CHANGED
@@ -85,7 +85,7 @@ export type ReaderPositionals = {
85
85
  * - End-of-options separator: `--` — all subsequent tokens are treated as positionals.
86
86
  *
87
87
  * In most cases you do not need to use `ReaderArgs` directly; it is created internally
88
- * by {@link runAsCliAndExit}. It is exposed for advanced use cases such as building
88
+ * by {@link runAndExit}. It is exposed for advanced use cases such as building
89
89
  * custom runners.
90
90
  */
91
91
  export class ReaderArgs {
package/src/lib/Run.ts CHANGED
@@ -16,7 +16,7 @@ import { usageToStyledLines } from "./Usage";
16
16
  * - `1` — Argument parsing failed (a usage summary is also printed to stderr), or the
17
17
  * command threw an unhandled execution error.
18
18
  *
19
- * **Built-in flags (opt-out):**
19
+ * **Built-in flags:**
20
20
  * - `--help` — Enabled by default (`usageOnHelp: true`). Prints the usage summary to
21
21
  * stdout and exits with code `0`. This flag takes precedence over `--version`.
22
22
  * - `--version` — Enabled when `buildVersion` is provided. Prints `<cliName> <version>`
@@ -44,10 +44,8 @@ import { usageToStyledLines } from "./Usage";
44
44
  * stderr before the error message whenever argument parsing fails.
45
45
  * @param options.buildVersion - When provided, registers a `--version` flag that prints
46
46
  * `<cliName> <buildVersion>` to stdout and exits with code `0`.
47
- * @param options.onExecutionError - Custom handler for errors thrown during command
48
- * execution. If omitted, the error is printed to stderr via {@link TypoSupport}.
49
- * @param options.onLogStdOut - Overrides the standard output sink (default: `console.log`).
50
- * @param options.onLogStdErr - Overrides the standard error sink (default: `console.error`).
47
+ * @param options.onError - Custom handler for errors thrown during command execution.
48
+ * If omitted, the error is printed to stderr via {@link TypoSupport}.
51
49
  * @param options.onExit - Overrides the process exit function (default: `process.exit`).
52
50
  * Useful for testing — supply a function that throws or captures the exit code instead
53
51
  * of actually terminating the process.
@@ -56,7 +54,7 @@ import { usageToStyledLines } from "./Usage";
56
54
  *
57
55
  * @example
58
56
  * ```ts
59
- * import { runAsCliAndExit, command, operation, positionalRequired, typeString } from "cli-kiss";
57
+ * import { runAndExit, command, operation, positionalRequired, typeString } from "cli-kiss";
60
58
  *
61
59
  * const greetCommand = command(
62
60
  * { description: "Greet someone" },
@@ -68,12 +66,12 @@ import { usageToStyledLines } from "./Usage";
68
66
  * ),
69
67
  * );
70
68
  *
71
- * await runAsCliAndExit("greet", process.argv.slice(2), undefined, greetCommand, {
69
+ * await runAndExit("greet", process.argv.slice(2), undefined, greetCommand, {
72
70
  * buildVersion: "1.0.0",
73
71
  * });
74
72
  * ```
75
73
  */
76
- export async function runAsCliAndExit<Context>(
74
+ export async function runAndExit<Context>(
77
75
  cliName: Lowercase<string>,
78
76
  cliArgs: ReadonlyArray<string>,
79
77
  context: Context,
@@ -83,9 +81,7 @@ export async function runAsCliAndExit<Context>(
83
81
  usageOnHelp?: boolean | undefined;
84
82
  usageOnError?: boolean | undefined;
85
83
  buildVersion?: string | undefined;
86
- onExecutionError?: ((error: unknown) => void) | undefined;
87
- onLogStdOut?: ((message: string) => void) | undefined;
88
- onLogStdErr?: ((message: string) => void) | undefined;
84
+ onError?: ((error: unknown) => void) | undefined;
89
85
  onExit?: ((code: number) => never) | undefined;
90
86
  },
91
87
  ): Promise<never> {
@@ -114,17 +110,6 @@ export async function runAsCliAndExit<Context>(
114
110
  longs: ["completion"],
115
111
  });
116
112
  */
117
- const typoSupport =
118
- options?.useTtyColors === undefined
119
- ? TypoSupport.inferFromProcess()
120
- : options.useTtyColors === "mock"
121
- ? TypoSupport.mock()
122
- : options.useTtyColors
123
- ? TypoSupport.tty()
124
- : TypoSupport.none();
125
- const onLogStdOut = options?.onLogStdOut ?? console.log;
126
- const onLogStdErr = options?.onLogStdErr ?? console.error;
127
- const onExit = options?.onExit ?? process.exit;
128
113
  const commandFactory = command.createFactory(readerArgs);
129
114
  while (true) {
130
115
  try {
@@ -134,15 +119,24 @@ export async function runAsCliAndExit<Context>(
134
119
  }
135
120
  } catch (_) {}
136
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();
137
131
  if (usageOnHelp) {
138
132
  if (readerArgs.getOptionValues("--help" as any).length > 0) {
139
- onLogStdOut(computeUsageString(cliName, commandFactory, typoSupport));
133
+ console.log(computeUsageString(cliName, commandFactory, typoSupport));
140
134
  return onExit(0);
141
135
  }
142
136
  }
143
137
  if (buildVersion) {
144
138
  if (readerArgs.getOptionValues("--version" as any).length > 0) {
145
- onLogStdOut([cliName, buildVersion].join(" "));
139
+ console.log([cliName, buildVersion].join(" "));
146
140
  return onExit(0);
147
141
  }
148
142
  }
@@ -152,18 +146,18 @@ export async function runAsCliAndExit<Context>(
152
146
  await commandInstance.executeWithContext(context);
153
147
  return onExit(0);
154
148
  } catch (executionError) {
155
- if (options?.onExecutionError) {
156
- options.onExecutionError(executionError);
149
+ if (options?.onError) {
150
+ options.onError(executionError);
157
151
  } else {
158
- onLogStdErr(typoSupport.computeStyledErrorMessage(executionError));
152
+ console.error(typoSupport.computeStyledErrorMessage(executionError));
159
153
  }
160
154
  return onExit(1);
161
155
  }
162
156
  } catch (parsingError) {
163
157
  if (options?.usageOnError ?? true) {
164
- onLogStdErr(computeUsageString(cliName, commandFactory, typoSupport));
158
+ console.error(computeUsageString(cliName, commandFactory, typoSupport));
165
159
  }
166
- onLogStdErr(typoSupport.computeStyledErrorMessage(parsingError));
160
+ console.error(typoSupport.computeStyledErrorMessage(parsingError));
167
161
  return onExit(1);
168
162
  }
169
163
  }
package/src/lib/Type.ts CHANGED
@@ -84,6 +84,7 @@ export const typeBoolean: Type<boolean> = {
84
84
  * @example
85
85
  * ```ts
86
86
  * typeDate.decoder("2024-01-15") // → Date object for 2024-01-15
87
+ * typeDate.decoder("2024-01-15T13:45:30Z") // → Date object for 2024-01-15 13:45:30 UTC
87
88
  * typeDate.decoder("not a date") // throws TypoError
88
89
  * ```
89
90
  */
package/src/lib/Typo.ts CHANGED
@@ -368,9 +368,8 @@ export class TypoError extends Error {
368
368
  * - {@link TypoSupport.inferFromProcess} — auto-detects based on `process.stdout.isTTY`
369
369
  * and the `FORCE_COLOR` / `NO_COLOR` environment variables.
370
370
  *
371
- * `TypoSupport` is consumed by {@link runAsCliAndExit} (via the `useTtyColors` option)
372
- * and can also be used directly when building custom usage renderers with
373
- * {@link usageToStyledLines}.
371
+ * `TypoSupport` is consumed by {@link runAndExit} (via the `useTtyColors` option)
372
+ * and can also be used directly when building custom usage renderers with {@link usageToStyledLines}.
374
373
  */
375
374
  export class TypoSupport {
376
375
  #kind: "none" | "tty" | "mock";
package/src/lib/Usage.ts CHANGED
@@ -39,8 +39,7 @@ import {
39
39
  * in each column sets the width for the entire section.
40
40
  *
41
41
  * @param params.cliName - The CLI program name shown at the start of the usage line.
42
- * @param params.commandUsage - The usage model produced by
43
- * {@link CommandFactory.generateUsage}.
42
+ * @param params.commandUsage - The usage model produced by {@link CommandFactory.generateUsage}.
44
43
  * @param params.typoSupport - Controls color/styling of the output.
45
44
  * @returns An ordered array of strings, one per output line (including a trailing
46
45
  * empty string for the blank line at the end).
@@ -50,7 +49,7 @@ import {
50
49
  * const lines = usageToStyledLines({
51
50
  * cliName: "my-cli",
52
51
  * commandUsage: commandFactory.generateUsage(),
53
- * typoSupport: TypoSupport.none(),
52
+ * typoSupport: TypoSupport.tty(),
54
53
  * });
55
54
  * process.stdout.write(lines.join("\n"));
56
55
  * ```
@@ -9,7 +9,7 @@ import {
9
9
  positionalOptional,
10
10
  positionalRequired,
11
11
  positionalVariadics,
12
- runAsCliAndExit,
12
+ runAndExit,
13
13
  typeConverted,
14
14
  typeOneOf,
15
15
  typeString,
@@ -324,7 +324,7 @@ async function testCase(
324
324
  ],
325
325
  },
326
326
  async () => {
327
- onLogStdOut.call("Has executed root command");
327
+ console.log("Has executed root command");
328
328
  },
329
329
  ),
330
330
  {
@@ -359,17 +359,17 @@ async function testCase(
359
359
  ],
360
360
  },
361
361
  async () => {
362
- onLogStdOut.call("Has executed subcommand");
362
+ console.log("Has executed subcommand");
363
363
  },
364
364
  ),
365
365
  ),
366
366
  },
367
367
  );
368
- await runAsCliAndExit("my-cli", args, null, cmd, {
368
+ console.log = onLogStdOut.call;
369
+ console.error = onLogStdErr.call;
370
+ await runAndExit("my-cli", args, null, cmd, {
369
371
  buildVersion: "1.0.0",
370
372
  useTtyColors: false,
371
- onLogStdOut: onLogStdOut.call,
372
- onLogStdErr: onLogStdErr.call,
373
373
  onExit: onExit.call,
374
374
  });
375
375
  expect({
@@ -5,7 +5,7 @@ import {
5
5
  optionFlag,
6
6
  optionRepeatable,
7
7
  optionSingleValue,
8
- runAsCliAndExit,
8
+ runAndExit,
9
9
  typeNumber,
10
10
  typeUrl,
11
11
  } from "../src";
@@ -34,43 +34,43 @@ it("run", async () => {
34
34
  });
35
35
 
36
36
  async function testCase(args: Array<string>, error: string) {
37
+ const onLogStdOut = makeMocked<string, void>([]);
37
38
  const onLogStdErr = makeMocked<string, void>([null as unknown as void]);
38
39
  const onExit = makeMocked<number, never>([null as never]);
39
- await runAsCliAndExit(
40
- "my-cli",
41
- args,
42
- null,
43
- command<void, void>(
44
- { description: "" },
45
- operation(
46
- {
47
- options: {
48
- optionFlag: optionFlag({ long: "flag" }),
49
- optionSingleValue: optionSingleValue({
50
- label: "LOCATION",
51
- long: "single-value",
52
- type: typeUrl,
53
- default: () => undefined,
54
- }),
55
- optionRepeatable: optionRepeatable({
56
- label: "INDEX",
57
- long: "repeatable",
58
- type: typeNumber,
59
- }),
60
- },
61
- positionals: [],
40
+ const rootCommand = command<void, void>(
41
+ { description: "" },
42
+ operation(
43
+ {
44
+ options: {
45
+ optionFlag: optionFlag({
46
+ long: "flag",
47
+ }),
48
+ optionSingleValue: optionSingleValue({
49
+ label: "LOCATION",
50
+ long: "single-value",
51
+ type: typeUrl,
52
+ default: () => undefined,
53
+ }),
54
+ optionRepeatable: optionRepeatable({
55
+ label: "INDEX",
56
+ long: "repeatable",
57
+ type: typeNumber,
58
+ }),
62
59
  },
63
- async (_, _inputs) => {},
64
- ),
60
+ positionals: [],
61
+ },
62
+ async () => {},
65
63
  ),
66
- {
67
- buildVersion: "1.0.0",
68
- usageOnError: false,
69
- useTtyColors: "mock",
70
- onLogStdErr: onLogStdErr.call,
71
- onExit: onExit.call,
72
- },
73
64
  );
65
+ console.log = onLogStdOut.call;
66
+ console.error = onLogStdErr.call;
67
+ await runAndExit("my-cli", args, null, rootCommand, {
68
+ buildVersion: "1.0.0",
69
+ usageOnError: false,
70
+ useTtyColors: "mock",
71
+ onExit: onExit.call,
72
+ });
73
+ expect(onLogStdOut.history).toEqual([]);
74
74
  expect(onLogStdErr.history).toEqual([error]);
75
75
  expect(onExit.history).toEqual([1]);
76
76
  }