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.
package/src/lib/Run.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import { Command, CommandDecoder } from "./Command";
2
+ import { optionFlag, optionSingleValue } from "./Option";
2
3
  import { ReaderArgs } from "./Reader";
4
+ import { typeChoice } from "./Type";
3
5
  import { TypoSupport } from "./Typo";
4
6
  import { usageToStyledLines } from "./Usage";
5
7
 
@@ -7,17 +9,17 @@ import { usageToStyledLines } from "./Usage";
7
9
  * Main entry point: parses CLI arguments, executes the matched command, and exits.
8
10
  * Handles `--help`, `--version`, usage-on-error, and exit codes.
9
11
  *
10
- * Exit codes: `0` on success / `--help` / `--version`; `1` on parse or execution error.
12
+ * Exit codes:
13
+ * - `0` on success / `--help` / `--version`
14
+ * - `1` on parse error or execution error.
11
15
  *
12
- * @typeParam Context - Passed unchanged to the command handler; use to inject dependencies.
16
+ * @typeParam Context - Forwarded unchanged to the handler.
13
17
  *
14
18
  * @param cliName - Program name used in usage and `--version` output.
15
19
  * @param cliArgs - Raw arguments, typically `process.argv.slice(2)`.
16
- * @param context - Forwarded to the command handler, injected dependencies.
20
+ * @param context - Forwarded to the handler.
17
21
  * @param command - Root {@link Command}.
18
- * @param options - Optional runner configuration.
19
- * @param options.useTtyColors - Color mode: `true` (always), `false` (never),
20
- * `"mock"` (snapshot-friendly), `undefined` (auto-detect from env).
22
+ * @param options.colorSetup - Configures color support; enables `--color` flag if set to `"flag"`.
21
23
  * @param options.usageOnHelp - Enables `--help` flag (default `true`).
22
24
  * @param options.usageOnError - Prints usage to stderr on parse error (default `true`).
23
25
  * @param options.buildVersion - Enables `--version`; prints `<cliName> <buildVersion>`.
@@ -28,13 +30,13 @@ import { usageToStyledLines } from "./Usage";
28
30
  *
29
31
  * @example
30
32
  * ```ts
31
- * import { runAndExit, command, operation, positionalRequired, typeString } from "cli-kiss";
33
+ * import { runAndExit, command, operation, positionalRequired, type } from "cli-kiss";
32
34
  *
33
35
  * const greetCommand = command(
34
36
  * { description: "Greet someone" },
35
37
  * operation(
36
- * { options: {}, positionals: [positionalRequired({ type: typeString, label: "NAME" })] },
37
- * async (_ctx, { positionals: [name] }) => {
38
+ * { options: {}, positionals: [positionalRequired({ type: type("name") })] },
39
+ * async function (_ctx, { positionals: [name] }) {
38
40
  * console.log(`Hello, ${name}!`);
39
41
  * },
40
42
  * ),
@@ -46,12 +48,12 @@ import { usageToStyledLines } from "./Usage";
46
48
  * ```
47
49
  */
48
50
  export async function runAndExit<Context>(
49
- cliName: Lowercase<string>,
51
+ cliName: string,
50
52
  cliArgs: ReadonlyArray<string>,
51
53
  context: Context,
52
54
  command: Command<Context, void>,
53
55
  options?: {
54
- useTtyColors?: boolean | undefined | "mock";
56
+ colorSetup?: "flag" | "env" | "always" | "never" | "mock" | undefined;
55
57
  usageOnHelp?: boolean | undefined;
56
58
  usageOnError?: boolean | undefined;
57
59
  buildVersion?: string | undefined;
@@ -60,20 +62,58 @@ export async function runAndExit<Context>(
60
62
  },
61
63
  ): Promise<never> {
62
64
  const readerArgs = new ReaderArgs(cliArgs);
63
- const usageOnHelp = options?.usageOnHelp ?? true;
64
- if (usageOnHelp) {
65
- readerArgs.registerOption({
66
- shorts: [],
67
- longs: ["help"],
68
- valued: false,
65
+ const preprocessors = new Array<
66
+ (commandDecoder: CommandDecoder<Context, void>) => undefined | number
67
+ >();
68
+ let typoSupport = TypoSupport.none();
69
+ const colorSetup = options?.colorSetup ?? "flag";
70
+ if (colorSetup === "flag") {
71
+ const colorOption = optionSingleValue<"auto" | "always" | "never" | "mock">(
72
+ {
73
+ long: "color",
74
+ type: typeChoice("color-mode", ["auto", "always", "never", "mock"]),
75
+ defaultWhenNotDefined: () => "auto",
76
+ defaultWhenNotInlined: () => "always",
77
+ },
78
+ ).registerAndMakeDecoder(readerArgs);
79
+ preprocessors.push(() => {
80
+ try {
81
+ typoSupport = computeTypoSupport(colorOption.getAndDecodeValue());
82
+ } catch (error) {
83
+ typoSupport = TypoSupport.inferFromEnv();
84
+ throw error;
85
+ }
86
+ return undefined;
87
+ });
88
+ } else {
89
+ if (colorSetup === "env") {
90
+ typoSupport = TypoSupport.inferFromEnv();
91
+ } else {
92
+ typoSupport = computeTypoSupport(colorSetup);
93
+ }
94
+ }
95
+ if (options?.usageOnHelp ?? true) {
96
+ const helpOption = optionFlag({ long: "help" }).registerAndMakeDecoder(
97
+ readerArgs,
98
+ );
99
+ preprocessors.push((commandDecoder) => {
100
+ if (!helpOption.getAndDecodeValue()) {
101
+ return undefined;
102
+ }
103
+ console.log(computeUsageString(cliName, commandDecoder, typoSupport));
104
+ return 0;
69
105
  });
70
106
  }
71
- const buildVersion = options?.buildVersion;
72
- if (buildVersion) {
73
- readerArgs.registerOption({
74
- shorts: [],
75
- longs: ["version"],
76
- valued: false,
107
+ if (options?.buildVersion) {
108
+ const versionOption = optionFlag({
109
+ long: "version",
110
+ }).registerAndMakeDecoder(readerArgs);
111
+ preprocessors.push(() => {
112
+ if (!versionOption.getAndDecodeValue()) {
113
+ return undefined;
114
+ }
115
+ console.log([cliName, options.buildVersion].join(" "));
116
+ return 0;
77
117
  });
78
118
  }
79
119
  /*
@@ -94,27 +134,13 @@ export async function runAndExit<Context>(
94
134
  } catch (_) {}
95
135
  }
96
136
  const onExit = options?.onExit ?? process.exit;
97
- const typoSupport =
98
- options?.useTtyColors === undefined
99
- ? TypoSupport.inferFromProcess()
100
- : options.useTtyColors === "mock"
101
- ? TypoSupport.mock()
102
- : options.useTtyColors
103
- ? TypoSupport.tty()
104
- : TypoSupport.none();
105
- if (usageOnHelp) {
106
- if (readerArgs.getOptionValues("--help" as any).length > 0) {
107
- console.log(computeUsageString(cliName, commandDecoder, typoSupport));
108
- return onExit(0);
109
- }
110
- }
111
- if (buildVersion) {
112
- if (readerArgs.getOptionValues("--version" as any).length > 0) {
113
- console.log([cliName, buildVersion].join(" "));
114
- return onExit(0);
115
- }
116
- }
117
137
  try {
138
+ for (const preprocessor of preprocessors) {
139
+ const preprocessorResult = preprocessor(commandDecoder);
140
+ if (preprocessorResult !== undefined) {
141
+ return onExit(preprocessorResult);
142
+ }
143
+ }
118
144
  const commandInterpreter = commandDecoder.decodeAndMakeInterpreter();
119
145
  try {
120
146
  await commandInterpreter.executeWithContext(context);
@@ -145,13 +171,28 @@ function handleError(
145
171
  }
146
172
 
147
173
  function computeUsageString<Context, Result>(
148
- cliName: Lowercase<string>,
174
+ cliName: string,
149
175
  commandDecoder: CommandDecoder<Context, Result>,
150
176
  typoSupport: TypoSupport,
151
177
  ) {
152
178
  return usageToStyledLines({
153
179
  cliName,
154
- commandUsage: commandDecoder.generateUsage(),
180
+ usage: commandDecoder.generateUsage(),
155
181
  typoSupport,
156
182
  }).join("\n");
157
183
  }
184
+
185
+ function computeTypoSupport(
186
+ colorMode: "auto" | "always" | "never" | "mock",
187
+ ): TypoSupport {
188
+ switch (colorMode) {
189
+ case "auto":
190
+ return TypoSupport.inferFromEnv();
191
+ case "always":
192
+ return TypoSupport.tty();
193
+ case "never":
194
+ return TypoSupport.none();
195
+ case "mock":
196
+ return TypoSupport.mock();
197
+ }
198
+ }