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.
- package/.github/workflows/docs.yml +35 -0
- package/README.md +12 -1
- package/dist/index.d.ts +22 -28
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/docs/.vitepress/config.mts +41 -0
- package/docs/guide/01_getting_started.md +116 -0
- package/docs/guide/02_commands.md +157 -0
- package/docs/guide/03_options.md +111 -0
- package/docs/guide/04_positionals.md +118 -0
- package/docs/guide/05_types.md +134 -0
- package/docs/guide/06_run.md +161 -0
- package/docs/index.md +30 -0
- package/package.json +6 -3
- package/src/index.ts +0 -1
- package/src/lib/Command.ts +6 -7
- package/src/lib/Operation.ts +2 -2
- package/src/lib/Reader.ts +1 -1
- package/src/lib/Run.ts +23 -29
- package/src/lib/Type.ts +1 -0
- package/src/lib/Typo.ts +2 -3
- package/src/lib/Usage.ts +2 -3
- package/tests/unit.runner.cycle.ts +6 -6
- package/tests/unit.runner.errors.ts +33 -33
|
@@ -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
|
|
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
package/src/lib/Command.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
package/src/lib/Operation.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
48
|
-
*
|
|
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 {
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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?.
|
|
156
|
-
options.
|
|
149
|
+
if (options?.onError) {
|
|
150
|
+
options.onError(executionError);
|
|
157
151
|
} else {
|
|
158
|
-
|
|
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
|
-
|
|
158
|
+
console.error(computeUsageString(cliName, commandFactory, typoSupport));
|
|
165
159
|
}
|
|
166
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
362
|
+
console.log("Has executed subcommand");
|
|
363
363
|
},
|
|
364
364
|
),
|
|
365
365
|
),
|
|
366
366
|
},
|
|
367
367
|
);
|
|
368
|
-
|
|
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
|
-
|
|
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
|
-
|
|
40
|
-
"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
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
|
}
|