cli-kiss 0.2.4 → 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/dist/index.d.ts +150 -164
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/docs/.vitepress/config.mts +1 -0
- package/docs/guide/01_getting_started.md +12 -13
- package/docs/guide/02_commands.md +12 -29
- package/docs/guide/03_options.md +16 -25
- package/docs/guide/04_positionals.md +45 -55
- package/docs/guide/05_types.md +66 -66
- package/docs/guide/06_run.md +28 -40
- package/docs/public/favicon.ico +0 -0
- package/docs/public/hero.png +0 -0
- package/package.json +1 -1
- package/src/index.ts +0 -2
- package/src/lib/Command.ts +14 -35
- package/src/lib/Operation.ts +13 -4
- package/src/lib/Option.ts +118 -162
- package/src/lib/Positional.ts +37 -62
- package/src/lib/Reader.ts +3 -3
- package/src/lib/Run.ts +74 -46
- package/src/lib/Type.ts +227 -141
- package/src/lib/Typo.ts +36 -24
- package/src/lib/Usage.ts +27 -42
- package/tests/unit.Reader.parsings.ts +50 -0
- package/tests/unit.command.execute.ts +13 -13
- package/tests/unit.command.usage.ts +60 -54
- package/tests/unit.runner.colors.ts +197 -0
- package/tests/unit.runner.cycle.ts +69 -55
- package/tests/unit.runner.errors.ts +12 -20
package/docs/guide/06_run.md
CHANGED
|
@@ -8,24 +8,24 @@
|
|
|
8
8
|
await runAndExit(cliName, cliArgs, context, command, options?);
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
| Parameter | Type
|
|
12
|
-
| --------- |
|
|
13
|
-
| `cliName` | `
|
|
14
|
-
| `cliArgs` | `ReadonlyArray<string>`
|
|
15
|
-
| `context` | `Context`
|
|
16
|
-
| `command` | `Command<Context, void>`
|
|
17
|
-
| `options` | `object?`
|
|
11
|
+
| Parameter | Type | Description |
|
|
12
|
+
| --------- | ------------------------ | ------------------------------------------------- |
|
|
13
|
+
| `cliName` | `string` | Program name used in help and `--version` output |
|
|
14
|
+
| `cliArgs` | `ReadonlyArray<string>` | Raw arguments — typically `process.argv.slice(2)` |
|
|
15
|
+
| `context` | `Context` | Value forwarded to every command handler |
|
|
16
|
+
| `command` | `Command<Context, void>` | The root command |
|
|
17
|
+
| `options` | `object?` | See below |
|
|
18
18
|
|
|
19
19
|
### Options
|
|
20
20
|
|
|
21
|
-
| Option | Type
|
|
22
|
-
| -------------- |
|
|
23
|
-
| `buildVersion` | `string?`
|
|
24
|
-
| `usageOnHelp` | `boolean?`
|
|
25
|
-
| `usageOnError` | `boolean?`
|
|
26
|
-
| `
|
|
27
|
-
| `onError` | `(error: unknown) => void`
|
|
28
|
-
| `onExit` | `(code: number) => never`
|
|
21
|
+
| Option | Type | Default | Description |
|
|
22
|
+
| -------------- | ------------------------------------------- | -------------- | ------------------------------------------------------------------------------------------- |
|
|
23
|
+
| `buildVersion` | `string?` | — | Enables `--version` flag; prints `<cliName> <buildVersion>` |
|
|
24
|
+
| `usageOnHelp` | `boolean?` | `true` | Enables `--help` flag |
|
|
25
|
+
| `usageOnError` | `boolean?` | `true` | Prints usage to stderr when parsing fails |
|
|
26
|
+
| `colorSetup` | `"flag"\|"env"\|"always"\|"never"\|"mock"?` | `"flag"` | Color mode: `"flag"` adds a `--color` option; `"env"` reads env vars; others force the mode |
|
|
27
|
+
| `onError` | `(error: unknown) => void` | — | Custom handler for parse and execution errors |
|
|
28
|
+
| `onExit` | `(code: number) => never` | `process.exit` | Override for testing |
|
|
29
29
|
|
|
30
30
|
### Exit codes
|
|
31
31
|
|
|
@@ -37,18 +37,6 @@ await runAndExit(cliName, cliArgs, context, command, options?);
|
|
|
37
37
|
## Full example
|
|
38
38
|
|
|
39
39
|
```ts
|
|
40
|
-
import {
|
|
41
|
-
command,
|
|
42
|
-
commandWithSubcommands,
|
|
43
|
-
operation,
|
|
44
|
-
optionFlag,
|
|
45
|
-
optionSingleValue,
|
|
46
|
-
positionalRequired,
|
|
47
|
-
runAndExit,
|
|
48
|
-
typeString,
|
|
49
|
-
typeUrl,
|
|
50
|
-
} from "cli-kiss";
|
|
51
|
-
|
|
52
40
|
type Ctx = { db: string };
|
|
53
41
|
|
|
54
42
|
const deployCmd = command(
|
|
@@ -77,9 +65,9 @@ const rootCmd = commandWithSubcommands(
|
|
|
77
65
|
options: {
|
|
78
66
|
dbUrl: optionSingleValue({
|
|
79
67
|
long: "db",
|
|
80
|
-
type: typeUrl,
|
|
68
|
+
type: typeUrl(),
|
|
81
69
|
description: "Database URL",
|
|
82
|
-
|
|
70
|
+
defaultWhenNotDefined: () => new URL("postgres://localhost/mydb"),
|
|
83
71
|
}),
|
|
84
72
|
},
|
|
85
73
|
positionals: [],
|
|
@@ -103,7 +91,7 @@ my-cli --help
|
|
|
103
91
|
```
|
|
104
92
|
|
|
105
93
|
```text
|
|
106
|
-
Usage: my-cli <
|
|
94
|
+
Usage: my-cli <subcommand>
|
|
107
95
|
|
|
108
96
|
My deployment CLI
|
|
109
97
|
|
|
@@ -111,7 +99,7 @@ Subcommands:
|
|
|
111
99
|
deploy Deploy to production
|
|
112
100
|
|
|
113
101
|
Options:
|
|
114
|
-
--db <
|
|
102
|
+
--db <url> Database URL
|
|
115
103
|
```
|
|
116
104
|
|
|
117
105
|
Try it
|
|
@@ -126,17 +114,21 @@ my-cli deploy --dry-run
|
|
|
126
114
|
|
|
127
115
|
## Color control
|
|
128
116
|
|
|
129
|
-
Colors are auto-detected
|
|
117
|
+
Colors are auto-detected by default (`colorSetup: "flag"` adds a `--color`
|
|
118
|
+
option). Override:
|
|
130
119
|
|
|
131
120
|
```ts
|
|
132
121
|
// Force colors on
|
|
133
|
-
await runAndExit("my-cli", args, ctx, cmd, {
|
|
122
|
+
await runAndExit("my-cli", args, ctx, cmd, { colorSetup: "always" });
|
|
134
123
|
|
|
135
124
|
// Force colors off (useful in CI)
|
|
136
|
-
await runAndExit("my-cli", args, ctx, cmd, {
|
|
125
|
+
await runAndExit("my-cli", args, ctx, cmd, { colorSetup: "never" });
|
|
126
|
+
|
|
127
|
+
// Read from env vars (FORCE_COLOR, NO_COLOR, MOCK_COLOR)
|
|
128
|
+
await runAndExit("my-cli", args, ctx, cmd, { colorSetup: "env" });
|
|
137
129
|
|
|
138
130
|
// Deterministic mock output (useful in snapshot tests)
|
|
139
|
-
await runAndExit("my-cli", args, ctx, cmd, {
|
|
131
|
+
await runAndExit("my-cli", args, ctx, cmd, { colorSetup: "mock" });
|
|
140
132
|
```
|
|
141
133
|
|
|
142
134
|
## Testing your CLI
|
|
@@ -144,17 +136,13 @@ await runAndExit("my-cli", args, ctx, cmd, { useTtyColors: "mock" });
|
|
|
144
136
|
Override `onExit` to prevent process exit during tests:
|
|
145
137
|
|
|
146
138
|
```ts
|
|
147
|
-
import { runAndExit } from "cli-kiss";
|
|
148
|
-
|
|
149
139
|
const exitCodes: number[] = [];
|
|
150
|
-
|
|
151
140
|
await runAndExit("my-cli", ["--help"], undefined, myCommand, {
|
|
152
|
-
|
|
141
|
+
colorSetup: "never",
|
|
153
142
|
onExit: (code) => {
|
|
154
143
|
exitCodes.push(code);
|
|
155
144
|
return undefined as never;
|
|
156
145
|
},
|
|
157
146
|
});
|
|
158
|
-
|
|
159
147
|
console.assert(exitCodes[0] === 0, "expected exit 0 for --help");
|
|
160
148
|
```
|
|
Binary file
|
package/docs/public/hero.png
CHANGED
|
Binary file
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
package/src/lib/Command.ts
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
typoStyleUserInput,
|
|
8
8
|
TypoText,
|
|
9
9
|
} from "./Typo";
|
|
10
|
-
import { UsageCommand
|
|
10
|
+
import { UsageCommand } from "./Usage";
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* A CLI command. Created with {@link command}, {@link commandWithSubcommands}, or {@link commandChained}.
|
|
@@ -93,8 +93,8 @@ export type CommandInformation = {
|
|
|
93
93
|
| { subcommand: string }
|
|
94
94
|
| {
|
|
95
95
|
option:
|
|
96
|
-
| { long: string;
|
|
97
|
-
| { short: string;
|
|
96
|
+
| { long: string; inlined?: string; separated?: Array<string> }
|
|
97
|
+
| { short: string; inlined?: string; separated?: Array<string> };
|
|
98
98
|
}
|
|
99
99
|
>;
|
|
100
100
|
}>;
|
|
@@ -115,7 +115,7 @@ export type CommandInformation = {
|
|
|
115
115
|
* const greet = command(
|
|
116
116
|
* { description: "Greet a user" },
|
|
117
117
|
* operation(
|
|
118
|
-
* { options: {}, positionals: [positionalRequired({ type:
|
|
118
|
+
* { options: {}, positionals: [positionalRequired({ type: type("name") })] },
|
|
119
119
|
* async (_ctx, { positionals: [name] }) => console.log(`Hello, ${name}!`),
|
|
120
120
|
* ),
|
|
121
121
|
* );
|
|
@@ -192,7 +192,7 @@ export function command<Context, Result>(
|
|
|
192
192
|
export function commandWithSubcommands<Context, Payload, Result>(
|
|
193
193
|
information: CommandInformation,
|
|
194
194
|
operation: Operation<Context, Payload>,
|
|
195
|
-
subcommands: { [subcommand:
|
|
195
|
+
subcommands: { [subcommand: string]: Command<Payload, Result> },
|
|
196
196
|
): Command<Context, Result> {
|
|
197
197
|
return {
|
|
198
198
|
getInformation() {
|
|
@@ -205,17 +205,16 @@ export function commandWithSubcommands<Context, Payload, Result>(
|
|
|
205
205
|
if (subcommandName === undefined) {
|
|
206
206
|
throw new TypoError(
|
|
207
207
|
new TypoText(
|
|
208
|
-
new TypoString(`<
|
|
208
|
+
new TypoString(`<subcommand>`, typoStyleUserInput),
|
|
209
209
|
new TypoString(`: Is required, but was not provided`),
|
|
210
210
|
),
|
|
211
211
|
);
|
|
212
212
|
}
|
|
213
|
-
const subcommandInput =
|
|
214
|
-
subcommands[subcommandName as Lowercase<string>];
|
|
213
|
+
const subcommandInput = subcommands[subcommandName];
|
|
215
214
|
if (subcommandInput === undefined) {
|
|
216
215
|
throw new TypoError(
|
|
217
216
|
new TypoText(
|
|
218
|
-
new TypoString(`<
|
|
217
|
+
new TypoString(`<subcommand>`, typoStyleUserInput),
|
|
219
218
|
new TypoString(`: Invalid value: `),
|
|
220
219
|
new TypoString(`"${subcommandName}"`, typoStyleQuote),
|
|
221
220
|
),
|
|
@@ -227,7 +226,7 @@ export function commandWithSubcommands<Context, Payload, Result>(
|
|
|
227
226
|
generateUsage() {
|
|
228
227
|
const subcommandUsage = subcommandDecoder.generateUsage();
|
|
229
228
|
const currentUsage = generateUsageLeaf(information, operation);
|
|
230
|
-
currentUsage.segments.push(
|
|
229
|
+
currentUsage.segments.push({ subcommand: subcommandName });
|
|
231
230
|
currentUsage.segments.push(...subcommandUsage.segments);
|
|
232
231
|
currentUsage.information = subcommandUsage.information;
|
|
233
232
|
currentUsage.positionals.push(...subcommandUsage.positionals);
|
|
@@ -253,7 +252,7 @@ export function commandWithSubcommands<Context, Payload, Result>(
|
|
|
253
252
|
return {
|
|
254
253
|
generateUsage() {
|
|
255
254
|
const currentUsage = generateUsageLeaf(information, operation);
|
|
256
|
-
currentUsage.segments.push(
|
|
255
|
+
currentUsage.segments.push({ positional: "<subcommand>" });
|
|
257
256
|
for (const [name, subcommand] of Object.entries(subcommands)) {
|
|
258
257
|
const { description, hint } = subcommand.getInformation();
|
|
259
258
|
currentUsage.subcommands.push({ name, description, hint });
|
|
@@ -281,18 +280,6 @@ export function commandWithSubcommands<Context, Payload, Result>(
|
|
|
281
280
|
* @param operation - Runs first; output becomes `subcommand`'s context.
|
|
282
281
|
* @param subcommand - Runs after `operation`.
|
|
283
282
|
* @returns A {@link Command} composing both stages.
|
|
284
|
-
*
|
|
285
|
-
* @example
|
|
286
|
-
* ```ts
|
|
287
|
-
* const authenticatedDeploy = commandChained(
|
|
288
|
-
* { description: "Authenticate then deploy" },
|
|
289
|
-
* operation(
|
|
290
|
-
* { options: { token: optionSingleValue({ long: "token", type: typeString, default: () => "" }) }, positionals: [] },
|
|
291
|
-
* async (_ctx, { options: { token } }) => ({ token }),
|
|
292
|
-
* ),
|
|
293
|
-
* command({ description: "Deploy" }, deployOperation),
|
|
294
|
-
* );
|
|
295
|
-
* ```
|
|
296
283
|
*/
|
|
297
284
|
export function commandChained<Context, Payload, Result>(
|
|
298
285
|
information: CommandInformation,
|
|
@@ -336,7 +323,7 @@ export function commandChained<Context, Payload, Result>(
|
|
|
336
323
|
return {
|
|
337
324
|
generateUsage() {
|
|
338
325
|
const currentUsage = generateUsageLeaf(information, operation);
|
|
339
|
-
currentUsage.segments.push(
|
|
326
|
+
currentUsage.segments.push({ positional: "[REST]..." });
|
|
340
327
|
return currentUsage;
|
|
341
328
|
},
|
|
342
329
|
decodeAndMakeInterpreter() {
|
|
@@ -348,23 +335,15 @@ export function commandChained<Context, Payload, Result>(
|
|
|
348
335
|
};
|
|
349
336
|
}
|
|
350
337
|
|
|
351
|
-
function segmentPositional(value: string): UsageSegment {
|
|
352
|
-
return { positional: value };
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
function segmentSubcommand(value: string): UsageSegment {
|
|
356
|
-
return { subcommand: value };
|
|
357
|
-
}
|
|
358
|
-
|
|
359
338
|
function generateUsageLeaf(
|
|
360
339
|
information: CommandInformation,
|
|
361
340
|
operation: Operation<any, any>,
|
|
362
341
|
): UsageCommand {
|
|
363
342
|
const { positionals, options } = operation.generateUsage();
|
|
364
343
|
return {
|
|
365
|
-
segments: positionals.map((positional) =>
|
|
366
|
-
|
|
367
|
-
),
|
|
344
|
+
segments: positionals.map((positional) => ({
|
|
345
|
+
positional: positional.label,
|
|
346
|
+
})),
|
|
368
347
|
information,
|
|
369
348
|
positionals,
|
|
370
349
|
subcommands: [],
|
package/src/lib/Operation.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Option, OptionDecoder } from "./Option";
|
|
2
2
|
import { Positional, PositionalDecoder } from "./Positional";
|
|
3
3
|
import { ReaderArgs } from "./Reader";
|
|
4
|
-
import {
|
|
4
|
+
import { UsageOption, UsagePositional } from "./Usage";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Options, positionals, and an async handler that together form the logic of a CLI command.
|
|
@@ -16,7 +16,16 @@ export type Operation<Context, Result> = {
|
|
|
16
16
|
/**
|
|
17
17
|
* Returns usage metadata without consuming any arguments.
|
|
18
18
|
*/
|
|
19
|
-
generateUsage():
|
|
19
|
+
generateUsage(): {
|
|
20
|
+
/**
|
|
21
|
+
* Registered options.
|
|
22
|
+
*/
|
|
23
|
+
options: Array<UsageOption>;
|
|
24
|
+
/**
|
|
25
|
+
* Declared positionals, in order.
|
|
26
|
+
*/
|
|
27
|
+
positionals: Array<UsagePositional>;
|
|
28
|
+
};
|
|
20
29
|
/**
|
|
21
30
|
* Consumes args from `readerArgs` and returns an {@link OperationDecoder}.
|
|
22
31
|
*/
|
|
@@ -74,10 +83,10 @@ export type OperationInterpreter<Context, Result> = {
|
|
|
74
83
|
* const greetOperation = operation(
|
|
75
84
|
* {
|
|
76
85
|
* options: {
|
|
77
|
-
* loud: optionFlag({ long: "loud", description: "Print in uppercase" }),
|
|
86
|
+
* loud: optionFlag({ long: "loud", description: "Print in uppercase", default: false }),
|
|
78
87
|
* },
|
|
79
88
|
* positionals: [
|
|
80
|
-
* positionalRequired({ type:
|
|
89
|
+
* positionalRequired({ type: type("name"), description: "Name to greet" }),
|
|
81
90
|
* ],
|
|
82
91
|
* },
|
|
83
92
|
* async function (_ctx, { options: { loud }, positionals: [name] }) {
|