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/README.md +1 -1
- package/dist/index.d.ts +696 -734
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/docs/.vitepress/config.mts +2 -3
- package/docs/.vitepress/theme/index.ts +4 -0
- package/docs/.vitepress/theme/style.css +4 -0
- package/docs/guide/01_getting_started.md +12 -13
- package/docs/guide/02_commands.md +71 -52
- package/docs/guide/03_options.md +25 -33
- 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/index.md +8 -3
- 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 +45 -123
- package/src/lib/Operation.ts +23 -32
- package/src/lib/Option.ts +150 -170
- package/src/lib/Positional.ts +44 -94
- package/src/lib/Reader.ts +123 -99
- package/src/lib/Run.ts +86 -45
- package/src/lib/Type.ts +246 -156
- package/src/lib/Typo.ts +98 -107
- package/src/lib/Usage.ts +163 -82
- package/tests/unit.Reader.aliases.ts +31 -15
- package/tests/unit.Reader.commons.ts +99 -43
- package/tests/unit.Reader.parsings.ts +50 -0
- package/tests/unit.Reader.shortBig.ts +75 -31
- package/tests/unit.command.execute.ts +86 -43
- package/tests/unit.command.usage.ts +88 -82
- package/tests/unit.runner.colors.ts +197 -0
- package/tests/unit.runner.cycle.ts +77 -63
- package/tests/unit.runner.errors.ts +23 -15
package/docs/guide/05_types.md
CHANGED
|
@@ -1,79 +1,61 @@
|
|
|
1
1
|
# Types
|
|
2
2
|
|
|
3
|
-
A `Type<Value>` converts a raw CLI string into a typed value:
|
|
3
|
+
A `Type<Value>` converts a raw CLI string into a typed value:
|
|
4
|
+
|
|
5
|
+
- Contains a `content` label about the type of data being decoded
|
|
6
|
+
- Paired with a `decoder` function that throws if the value is invalid.
|
|
4
7
|
|
|
5
8
|
## Built-in types
|
|
6
9
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
|
10
|
-
|
|
|
11
|
-
| `
|
|
12
|
-
| `
|
|
13
|
-
| `
|
|
14
|
-
| `
|
|
10
|
+
All type factories accept an optional `name` parameter that overrides the label shown in help/errors.
|
|
11
|
+
|
|
12
|
+
| Type factory | Content type | Accepts |
|
|
13
|
+
| -------------- | ------------ | ---------------------------------------------------------------------------- |
|
|
14
|
+
| `type` | `string` | Any string |
|
|
15
|
+
| `typeBoolean` | `boolean` | `true/yes/on/1/y/t` → true, `false/no/off/0/n/f` → false (case-insensitive) |
|
|
16
|
+
| `typeNumber` | `number` | Integers, floats, scientific notation |
|
|
17
|
+
| `typeInteger` | `bigint` | Integer strings only |
|
|
18
|
+
| `typeDatetime` | `Date` | Any format accepted by `Date.parse` (ISO 8601 recommended) |
|
|
19
|
+
| `typeUrl` | `URL` | Absolute URLs |
|
|
20
|
+
| `typePath` | `string` | Non-empty path strings; optional sync existence check |
|
|
15
21
|
|
|
16
22
|
```ts
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
} from "cli-kiss";
|
|
25
|
-
|
|
26
|
-
typeString.decoder("hello"); // → "hello"
|
|
27
|
-
typeBoolean.decoder("yes"); // → true
|
|
28
|
-
typeNumber.decoder("3.14"); // → 3.14
|
|
29
|
-
typeInteger.decoder("9007199254740993"); // → 9007199254740993n
|
|
30
|
-
typeDate.decoder("2024-01-15"); // → Date object
|
|
31
|
-
typeUrl.decoder("https://example.com/path"); // → URL object
|
|
23
|
+
type("greeting").decoder("hello"); // → "hello"
|
|
24
|
+
typeBoolean("flag").decoder("yes"); // → true
|
|
25
|
+
typeNumber("pi").decoder("3.14"); // → 3.14
|
|
26
|
+
typeInteger("id").decoder("9007199254740993"); // → 9007199254740993n
|
|
27
|
+
typeDatetime("birthday").decoder("2024-01-15"); // → Date object
|
|
28
|
+
typeUrl("redirect").decoder("https://example.com/path"); // → URL object
|
|
29
|
+
typePath().decoder("/usr/bin"); // → "/usr/bin"
|
|
32
30
|
```
|
|
33
31
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
Accepts only a fixed set of strings:
|
|
32
|
+
`typePath` also accepts a second argument for existence checks:
|
|
37
33
|
|
|
38
34
|
```ts
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const typeEnv = typeOneOf("Environment", ["dev", "staging", "prod"]);
|
|
42
|
-
|
|
43
|
-
typeEnv.decoder("prod"); // → "prod"
|
|
44
|
-
typeEnv.decoder("unknown");
|
|
45
|
-
// Error: Invalid value: "unknown" (expected one of: "dev" | "staging" | "prod")
|
|
35
|
+
typePath("config", { checkSyncExistAs: "file" }); // throws if not a file
|
|
36
|
+
typePath("dir", { checkSyncExistAs: "directory" }); // throws if not a directory
|
|
46
37
|
```
|
|
47
38
|
|
|
48
|
-
## `
|
|
39
|
+
## `typeChoice` — string enum
|
|
49
40
|
|
|
50
|
-
|
|
41
|
+
Accepts only a fixed set of strings (case-insensitive by default):
|
|
51
42
|
|
|
52
43
|
```ts
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (n < 1 || n > 65535) throw new Error("Out of range");
|
|
59
|
-
return n;
|
|
60
|
-
},
|
|
61
|
-
});
|
|
62
|
-
// "--port 8080" → 8080
|
|
63
|
-
// "--port 99999" → Error: --port: <PORT>: Port: Out of range
|
|
44
|
+
const typeEnv = typeChoice("environment", ["dev", "staging", "prod"]);
|
|
45
|
+
typeEnv.decoder("prod"); // → "prod"
|
|
46
|
+
typeEnv.decoder("PROD"); // → "prod" (case-insensitive)
|
|
47
|
+
typeEnv.decoder("unknown");
|
|
48
|
+
// Error: Invalid value: "unknown" (expected one of: "dev" | "staging" | "prod")
|
|
64
49
|
```
|
|
65
50
|
|
|
66
|
-
|
|
51
|
+
Pass `true` as third argument to make matching case-sensitive.
|
|
67
52
|
|
|
68
53
|
## `typeTuple` — fixed-length delimited value
|
|
69
54
|
|
|
70
55
|
Splits a string into a fixed-length typed tuple:
|
|
71
56
|
|
|
72
57
|
```ts
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const typePoint = typeTuple([typeNumber, typeNumber]);
|
|
76
|
-
|
|
58
|
+
const typePoint = typeTuple([typeNumber(), typeNumber()]);
|
|
77
59
|
typePoint.decoder("3.14,2.71"); // → [3.14, 2.71]
|
|
78
60
|
typePoint.decoder("x,2"); // → Error: at 0: Number: Unable to parse: "x"
|
|
79
61
|
```
|
|
@@ -81,7 +63,7 @@ typePoint.decoder("x,2"); // → Error: at 0: Number: Unable to parse: "x"
|
|
|
81
63
|
The default separator is `","`. Pass a second argument to change it:
|
|
82
64
|
|
|
83
65
|
```ts
|
|
84
|
-
typeTuple([
|
|
66
|
+
typeTuple([type("name"), typeNumber()], ":");
|
|
85
67
|
// "foo:42" → ["foo", 42]
|
|
86
68
|
```
|
|
87
69
|
|
|
@@ -90,10 +72,7 @@ typeTuple([typeString, typeNumber], ":");
|
|
|
90
72
|
Splits a string into an array of typed values:
|
|
91
73
|
|
|
92
74
|
```ts
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const typeNumbers = typeList(typeNumber);
|
|
96
|
-
|
|
75
|
+
const typeNumbers = typeList(typeNumber());
|
|
97
76
|
typeNumbers.decoder("1,2,3"); // → [1, 2, 3]
|
|
98
77
|
typeNumbers.decoder("1,x,3"); // → Error: at 1: Number: Unable to parse: "x"
|
|
99
78
|
```
|
|
@@ -101,7 +80,7 @@ typeNumbers.decoder("1,x,3"); // → Error: at 1: Number: Unable to parse: "x"
|
|
|
101
80
|
Custom separator:
|
|
102
81
|
|
|
103
82
|
```ts
|
|
104
|
-
const typePaths = typeList(
|
|
83
|
+
const typePaths = typeList(typePath(), ":");
|
|
105
84
|
typePaths.decoder("/usr/bin:/usr/local/bin"); // → ["/usr/bin", "/usr/local/bin"]
|
|
106
85
|
```
|
|
107
86
|
|
|
@@ -112,21 +91,42 @@ over `typeList` when users should pass multiple values as separate flags
|
|
|
112
91
|
|
|
113
92
|
:::
|
|
114
93
|
|
|
94
|
+
## `typeConverted` — transform decoded value
|
|
95
|
+
|
|
96
|
+
Chains a base type with a transformation function:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
const typePort = typeConverted("port", typeNumber(), (n) => {
|
|
100
|
+
if (n < 1 || n > 65535) throw new Error("Out of range");
|
|
101
|
+
return n;
|
|
102
|
+
});
|
|
103
|
+
// "--port 8080" → 8080
|
|
104
|
+
// "--port 99999" → TypoError
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## `typeRenamed` — rename a type
|
|
108
|
+
|
|
109
|
+
Wraps a type with a different label for clearer errors:
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
const typeId = typeRenamed(typeInteger(), "user-id");
|
|
113
|
+
// errors show "user-id" instead of "integer"
|
|
114
|
+
```
|
|
115
|
+
|
|
115
116
|
## Custom types
|
|
116
117
|
|
|
117
118
|
Implement the `Type<Value>` interface directly:
|
|
118
119
|
|
|
119
120
|
```ts
|
|
120
|
-
import type { Type } from "cli-kiss";
|
|
121
|
-
|
|
122
121
|
const typeHexColor: Type<string> = {
|
|
123
|
-
content: "
|
|
122
|
+
content: "hex-color",
|
|
124
123
|
decoder(value) {
|
|
125
|
-
if (/^#[0-9a-fA-F]{6}$/.test(value))
|
|
126
|
-
|
|
124
|
+
if (/^#[0-9a-fA-F]{6}$/.test(value)) {
|
|
125
|
+
return value;
|
|
126
|
+
}
|
|
127
|
+
throw new Error(`Not a valid color: "${value}"`);
|
|
127
128
|
},
|
|
128
129
|
};
|
|
129
|
-
|
|
130
|
-
// "
|
|
131
|
-
// "--color red" → Error: --color: <HEXCOLOR>: HexColor: Not a valid value: "red"
|
|
130
|
+
// "#ff0000" → "#ff0000"
|
|
131
|
+
// "red" → Error: HexColor: Not a valid value: "red"
|
|
132
132
|
```
|
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
|
```
|
package/docs/index.md
CHANGED
|
@@ -4,10 +4,12 @@ layout: home
|
|
|
4
4
|
hero:
|
|
5
5
|
name: CLI-kiss
|
|
6
6
|
text: CLI for TypeScript.
|
|
7
|
+
|
|
7
8
|
tagline:
|
|
8
|
-
No bloat, no dependencies.<br/>Standard behavior users expect.<br
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
No bloat, no dependencies.<br/>Standard behavior users expect.<br/>Keep It Simple and Stupid, it just does the job.
|
|
10
|
+
|
|
11
|
+
image:
|
|
12
|
+
src: /hero.png
|
|
11
13
|
|
|
12
14
|
actions:
|
|
13
15
|
- theme: brand
|
|
@@ -19,12 +21,15 @@ hero:
|
|
|
19
21
|
|
|
20
22
|
features:
|
|
21
23
|
- title: Zero dependencies
|
|
24
|
+
icon: 📦
|
|
22
25
|
details:
|
|
23
26
|
Ships with no runtime dependencies.<br/>Pure TypeScript, 5kb bundled.
|
|
24
27
|
- title: Fully typed
|
|
28
|
+
icon: 🧠
|
|
25
29
|
details:
|
|
26
30
|
TypeScript first.<br/>Options and positionals inputs strongly typed.
|
|
27
31
|
- title: Composable
|
|
32
|
+
icon: 🧩
|
|
28
33
|
details:
|
|
29
34
|
Easily create nested subcommands.<br/>Build complex CLIs by nesting logic.
|
|
30
35
|
---
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED