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
|
@@ -8,10 +8,10 @@ npm install cli-kiss
|
|
|
8
8
|
|
|
9
9
|
## Your first CLI
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Example minimal "greet" CLI with:
|
|
12
12
|
|
|
13
|
-
- a required `
|
|
14
|
-
- optional `--loud` flag
|
|
13
|
+
- a required `name` positional argument
|
|
14
|
+
- optional `--loud` boolean flag
|
|
15
15
|
|
|
16
16
|
```ts
|
|
17
17
|
import {
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
optionFlag,
|
|
21
21
|
positionalRequired,
|
|
22
22
|
runAndExit,
|
|
23
|
-
|
|
23
|
+
type,
|
|
24
24
|
} from "cli-kiss";
|
|
25
25
|
|
|
26
26
|
const greetCommand = command(
|
|
@@ -32,20 +32,19 @@ const greetCommand = command(
|
|
|
32
32
|
},
|
|
33
33
|
positionals: [
|
|
34
34
|
positionalRequired({
|
|
35
|
-
type:
|
|
36
|
-
|
|
37
|
-
description: "The name to greet",
|
|
35
|
+
type: type("name"),
|
|
36
|
+
description: "The name of the person to greet",
|
|
38
37
|
}),
|
|
39
38
|
],
|
|
40
39
|
},
|
|
41
|
-
async (
|
|
40
|
+
async (_context, { options: { loud }, positionals: [name] }) => {
|
|
42
41
|
const message = `Hello, ${name}!`;
|
|
43
42
|
console.log(loud ? message.toUpperCase() : message);
|
|
44
43
|
},
|
|
45
44
|
),
|
|
46
45
|
);
|
|
47
46
|
|
|
48
|
-
await runAndExit("greet", process.argv.slice(2),
|
|
47
|
+
await runAndExit("greet", process.argv.slice(2), {}, greetCommand, {
|
|
49
48
|
buildVersion: "1.0.0",
|
|
50
49
|
});
|
|
51
50
|
```
|
|
@@ -70,19 +69,19 @@ greet --loud Alice
|
|
|
70
69
|
HELLO, ALICE!
|
|
71
70
|
```
|
|
72
71
|
|
|
73
|
-
Help (built-in):
|
|
72
|
+
Help and color (built-in):
|
|
74
73
|
|
|
75
74
|
```sh
|
|
76
|
-
greet --help
|
|
75
|
+
greet --help --color
|
|
77
76
|
```
|
|
78
77
|
|
|
79
78
|
```text
|
|
80
|
-
Usage: greet <
|
|
79
|
+
Usage: greet <name>
|
|
81
80
|
|
|
82
81
|
Greet someone
|
|
83
82
|
|
|
84
83
|
Positionals:
|
|
85
|
-
<
|
|
84
|
+
<name> The name of the person to greet
|
|
86
85
|
|
|
87
86
|
Options:
|
|
88
87
|
--loud[=no] Print in uppercase
|
|
@@ -7,14 +7,12 @@ Three factory functions cover every use-case.
|
|
|
7
7
|
No subcommands — directly runs an operation.
|
|
8
8
|
|
|
9
9
|
```ts
|
|
10
|
-
import { command, operation, positionalRequired, typeString } from "cli-kiss";
|
|
11
|
-
|
|
12
10
|
const greet = command(
|
|
13
11
|
{ description: "Greet a user" },
|
|
14
12
|
operation(
|
|
15
13
|
{
|
|
16
14
|
options: {},
|
|
17
|
-
positionals: [positionalRequired({ type:
|
|
15
|
+
positionals: [positionalRequired({ type: type("name") })],
|
|
18
16
|
},
|
|
19
17
|
async function (_ctx, { positionals: [name] }) {
|
|
20
18
|
console.log(`Hello, ${name}!`);
|
|
@@ -28,13 +26,6 @@ const greet = command(
|
|
|
28
26
|
User must pick one of several sub-actions.
|
|
29
27
|
|
|
30
28
|
```ts
|
|
31
|
-
import {
|
|
32
|
-
command,
|
|
33
|
-
commandWithSubcommands,
|
|
34
|
-
operation,
|
|
35
|
-
runAndExit,
|
|
36
|
-
} from "cli-kiss";
|
|
37
|
-
|
|
38
29
|
const rootCmd = commandWithSubcommands(
|
|
39
30
|
{ description: "My deployment CLI" },
|
|
40
31
|
// This operation runs before the subcommand is selected.
|
|
@@ -68,7 +59,7 @@ deploy-cli --help
|
|
|
68
59
|
```
|
|
69
60
|
|
|
70
61
|
```text
|
|
71
|
-
Usage: deploy-cli <
|
|
62
|
+
Usage: deploy-cli <subcommand>
|
|
72
63
|
|
|
73
64
|
My deployment CLI
|
|
74
65
|
|
|
@@ -79,21 +70,13 @@ Subcommands:
|
|
|
79
70
|
|
|
80
71
|
### Subcommand names
|
|
81
72
|
|
|
82
|
-
Keys are the tokens users
|
|
73
|
+
Keys are the tokens users must type.
|
|
83
74
|
|
|
84
75
|
## `commandChained` — sequential stages
|
|
85
76
|
|
|
86
77
|
Splits a command into reusable steps with no extra user-visible token.
|
|
87
78
|
|
|
88
79
|
```ts
|
|
89
|
-
import {
|
|
90
|
-
command,
|
|
91
|
-
commandChained,
|
|
92
|
-
operation,
|
|
93
|
-
optionSingleValue,
|
|
94
|
-
typeString,
|
|
95
|
-
} from "cli-kiss";
|
|
96
|
-
|
|
97
80
|
const authenticatedDeploy = commandChained(
|
|
98
81
|
{ description: "Authenticate then deploy" },
|
|
99
82
|
// Stage 1: parse a --token option and forward the token as context
|
|
@@ -102,9 +85,9 @@ const authenticatedDeploy = commandChained(
|
|
|
102
85
|
options: {
|
|
103
86
|
token: optionSingleValue({
|
|
104
87
|
long: "token",
|
|
105
|
-
type:
|
|
88
|
+
type: type("secret"),
|
|
106
89
|
description: "API token",
|
|
107
|
-
|
|
90
|
+
defaultWhenNotDefined: function () {
|
|
108
91
|
const t = process.env.API_TOKEN;
|
|
109
92
|
if (!t) throw new Error("API_TOKEN env var is required");
|
|
110
93
|
return t;
|
|
@@ -147,13 +130,13 @@ Each `Example` entry has:
|
|
|
147
130
|
|
|
148
131
|
Each `CommandArg` is one of:
|
|
149
132
|
|
|
150
|
-
| Shape
|
|
151
|
-
|
|
|
152
|
-
| `string`
|
|
153
|
-
| `{ positional: string }`
|
|
154
|
-
| `{ subcommand: string }`
|
|
155
|
-
| `{ option: { long: string;
|
|
156
|
-
| `{ option: { short: string;
|
|
133
|
+
| Shape | Renders as |
|
|
134
|
+
| ----------------------------------------------------------------------- | --------------------- |
|
|
135
|
+
| `string` | literal text |
|
|
136
|
+
| `{ positional: string }` | positional label |
|
|
137
|
+
| `{ subcommand: string }` | subcommand name |
|
|
138
|
+
| `{ option: { long: string; inlined?: string; separated?: string[] } }` | `--long[=val] [args]` |
|
|
139
|
+
| `{ option: { short: string; inlined?: string; separated?: string[] } }` | `-s[=val] [args]` |
|
|
157
140
|
|
|
158
141
|
```ts
|
|
159
142
|
command(
|
package/docs/guide/03_options.md
CHANGED
|
@@ -5,11 +5,9 @@ Named `--` arguments (or `-` for short forms). Declared in the `options` map of
|
|
|
5
5
|
|
|
6
6
|
## `optionFlag` — boolean toggle
|
|
7
7
|
|
|
8
|
-
Present or absent. Also accepts `--flag=true` / `--flag=no
|
|
8
|
+
Present or absent. Also accepts `--flag=true` / `--flag=no`.
|
|
9
9
|
|
|
10
10
|
```ts
|
|
11
|
-
import { optionFlag } from "cli-kiss";
|
|
12
|
-
|
|
13
11
|
const verbose = optionFlag({
|
|
14
12
|
long: "verbose",
|
|
15
13
|
short: "v",
|
|
@@ -23,7 +21,7 @@ const verbose = optionFlag({
|
|
|
23
21
|
|
|
24
22
|
| Parameter | Type | Description |
|
|
25
23
|
| ------------- | --------------------- | ------------------------------------ |
|
|
26
|
-
| `long` | `
|
|
24
|
+
| `long` | `string` | Long flag name (without `--`) |
|
|
27
25
|
| `short` | `string?` | Short flag name (without `-`) |
|
|
28
26
|
| `description` | `string?` | Help text |
|
|
29
27
|
| `hint` | `string?` | Short note in parentheses |
|
|
@@ -41,15 +39,12 @@ multiple values.
|
|
|
41
39
|
Exactly one typed value.
|
|
42
40
|
|
|
43
41
|
```ts
|
|
44
|
-
import { optionSingleValue, typeString } from "cli-kiss";
|
|
45
|
-
|
|
46
42
|
const output = optionSingleValue({
|
|
47
43
|
long: "output",
|
|
48
44
|
short: "o",
|
|
49
|
-
type:
|
|
50
|
-
label: "PATH",
|
|
45
|
+
type: typePath(),
|
|
51
46
|
description: "Output directory",
|
|
52
|
-
|
|
47
|
+
defaultWhenNotDefined: () => "dist/",
|
|
53
48
|
});
|
|
54
49
|
// --output dist/ → "dist/"
|
|
55
50
|
// --output=dist/ → "dist/"
|
|
@@ -57,29 +52,26 @@ const output = optionSingleValue({
|
|
|
57
52
|
// (absent) → "dist/"
|
|
58
53
|
```
|
|
59
54
|
|
|
60
|
-
| Parameter
|
|
61
|
-
|
|
|
62
|
-
| `long`
|
|
63
|
-
| `short`
|
|
64
|
-
| `type`
|
|
65
|
-
| `
|
|
66
|
-
| `
|
|
67
|
-
| `
|
|
68
|
-
| `
|
|
69
|
-
| `aliases`
|
|
55
|
+
| Parameter | Type | Description |
|
|
56
|
+
| ----------------------- | --------------------- | ---------------------------------------------------------------------------- |
|
|
57
|
+
| `long` | `string` | Long option name |
|
|
58
|
+
| `short` | `string?` | Short option name |
|
|
59
|
+
| `type` | `Type<Value>` | Decoder for the value |
|
|
60
|
+
| `description` | `string?` | Help text |
|
|
61
|
+
| `hint` | `string?` | Short note in parentheses |
|
|
62
|
+
| `defaultWhenNotDefined` | `() => Value` | Value when option is absent — **throw** to make it required |
|
|
63
|
+
| `defaultWhenNotInlined` | `() => Value?` | Value when option is present but has no inline value (e.g. `--output` alone) |
|
|
64
|
+
| `aliases` | `{ longs?, shorts? }` | Additional names |
|
|
70
65
|
|
|
71
66
|
## `optionRepeatable` — collect multiple values
|
|
72
67
|
|
|
73
68
|
Collects every occurrence into an array.
|
|
74
69
|
|
|
75
70
|
```ts
|
|
76
|
-
import { optionRepeatable, typeString } from "cli-kiss";
|
|
77
|
-
|
|
78
71
|
const files = optionRepeatable({
|
|
79
72
|
long: "file",
|
|
80
73
|
short: "f",
|
|
81
|
-
type:
|
|
82
|
-
label: "PATH",
|
|
74
|
+
type: typePath("FILE_PATH"),
|
|
83
75
|
description: "Input file (may be repeated)",
|
|
84
76
|
});
|
|
85
77
|
// --file a.ts --file b.ts → ["a.ts", "b.ts"]
|
|
@@ -88,10 +80,9 @@ const files = optionRepeatable({
|
|
|
88
80
|
|
|
89
81
|
| Parameter | Type | Description |
|
|
90
82
|
| ------------- | --------------------- | ---------------------------------- |
|
|
91
|
-
| `long` | `
|
|
83
|
+
| `long` | `string` | Long option name |
|
|
92
84
|
| `short` | `string?` | Short option name |
|
|
93
85
|
| `type` | `Type<Value>` | Decoder applied to each occurrence |
|
|
94
|
-
| `label` | `Uppercase<string>?` | Placeholder in help |
|
|
95
86
|
| `description` | `string?` | Help text |
|
|
96
87
|
| `hint` | `string?` | Short note in parentheses |
|
|
97
88
|
| `aliases` | `{ longs?, shorts? }` | Additional names |
|
|
@@ -1,69 +1,63 @@
|
|
|
1
1
|
# Positionals
|
|
2
2
|
|
|
3
|
-
Bare (non-`--`) arguments, consumed in order. Declared in the `positionals`
|
|
3
|
+
Bare (non-`--`) arguments, consumed in order. Declared in the `positionals`
|
|
4
|
+
array of [`operation`](/guide/02_commands).
|
|
4
5
|
|
|
5
6
|
## `positionalRequired` — must be present
|
|
6
7
|
|
|
7
8
|
Fails if missing.
|
|
8
9
|
|
|
9
10
|
```ts
|
|
10
|
-
import { positionalRequired, typeString } from "cli-kiss";
|
|
11
|
-
|
|
12
11
|
const name = positionalRequired({
|
|
13
|
-
type:
|
|
14
|
-
|
|
15
|
-
description: "The name to greet",
|
|
12
|
+
type: type("person"),
|
|
13
|
+
description: "The name of the person to greet",
|
|
16
14
|
});
|
|
17
|
-
//
|
|
18
|
-
//
|
|
15
|
+
// Usage:
|
|
16
|
+
// my-cli Alice → "Alice"
|
|
17
|
+
// my-cli → Error: <person>: Is required, but was not provided
|
|
19
18
|
```
|
|
20
19
|
|
|
21
|
-
| Parameter | Type
|
|
22
|
-
| ------------- |
|
|
23
|
-
| `type` | `Type<Value>`
|
|
24
|
-
| `
|
|
25
|
-
| `
|
|
26
|
-
| `hint` | `string?` | Short note in parentheses |
|
|
20
|
+
| Parameter | Type | Description |
|
|
21
|
+
| ------------- | ------------- | -------------------------------- |
|
|
22
|
+
| `type` | `Type<Value>` | Decoder for the raw string token |
|
|
23
|
+
| `description` | `string?` | Help text |
|
|
24
|
+
| `hint` | `string?` | Short note in parentheses |
|
|
27
25
|
|
|
28
26
|
## `positionalOptional` — may be absent
|
|
29
27
|
|
|
30
28
|
Falls back to a default when absent.
|
|
31
29
|
|
|
32
30
|
```ts
|
|
33
|
-
import { positionalOptional, typeString } from "cli-kiss";
|
|
34
|
-
|
|
35
31
|
const greeting = positionalOptional({
|
|
36
|
-
type:
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
type: type("greeting"),
|
|
33
|
+
description: "Custom greeting",
|
|
34
|
+
hint: "default to 'Hello'",
|
|
39
35
|
default: () => "Hello",
|
|
40
36
|
});
|
|
41
|
-
//
|
|
42
|
-
//
|
|
37
|
+
// Usage:
|
|
38
|
+
// my-cli → "Hello"
|
|
39
|
+
//. my-cli Howdy → "Howdy"
|
|
43
40
|
```
|
|
44
41
|
|
|
45
|
-
| Parameter | Type
|
|
46
|
-
| ------------- |
|
|
47
|
-
| `type` | `Type<Value>`
|
|
48
|
-
| `
|
|
49
|
-
| `
|
|
50
|
-
| `
|
|
51
|
-
| `default` | `() => Value` | Value when absent — **throw** to make it required |
|
|
42
|
+
| Parameter | Type | Description |
|
|
43
|
+
| ------------- | ------------- | ------------------------------------------------- |
|
|
44
|
+
| `type` | `Type<Value>` | Decoder for the raw string token |
|
|
45
|
+
| `description` | `string?` | Help text |
|
|
46
|
+
| `hint` | `string?` | Short note in parentheses |
|
|
47
|
+
| `default` | `() => Value` | Value when absent — **throw** to make it required |
|
|
52
48
|
|
|
53
49
|
## `positionalVariadics` — zero or more
|
|
54
50
|
|
|
55
51
|
Consumes all remaining tokens into an array.
|
|
56
52
|
|
|
57
53
|
```ts
|
|
58
|
-
import { positionalVariadics, typeString } from "cli-kiss";
|
|
59
|
-
|
|
60
54
|
const files = positionalVariadics({
|
|
61
|
-
type:
|
|
62
|
-
label: "FILE",
|
|
55
|
+
type: typePath(),
|
|
63
56
|
description: "Files to process",
|
|
64
57
|
});
|
|
65
|
-
//
|
|
66
|
-
//
|
|
58
|
+
// Usage:
|
|
59
|
+
// my-cli a.ts b.ts c.ts → ["a.ts", "b.ts", "c.ts"]
|
|
60
|
+
// my-cli → []
|
|
67
61
|
```
|
|
68
62
|
|
|
69
63
|
### End delimiter
|
|
@@ -72,21 +66,20 @@ Optionally stop collecting at a specific sentinel token:
|
|
|
72
66
|
|
|
73
67
|
```ts
|
|
74
68
|
const args = positionalVariadics({
|
|
75
|
-
type:
|
|
76
|
-
label: "ARG",
|
|
69
|
+
type: type("argument"),
|
|
77
70
|
endDelimiter: "STOP",
|
|
78
71
|
description: "Arguments (end with STOP)",
|
|
79
72
|
});
|
|
80
|
-
//
|
|
73
|
+
// Usage:
|
|
74
|
+
// my-cli foo bar STOP → ["foo", "bar"]
|
|
81
75
|
```
|
|
82
76
|
|
|
83
|
-
| Parameter | Type
|
|
84
|
-
| -------------- |
|
|
85
|
-
| `type` | `Type<Value>`
|
|
86
|
-
| `
|
|
87
|
-
| `
|
|
88
|
-
| `
|
|
89
|
-
| `endDelimiter` | `string?` | Sentinel token that stops collection |
|
|
77
|
+
| Parameter | Type | Description |
|
|
78
|
+
| -------------- | ------------- | ------------------------------------ |
|
|
79
|
+
| `type` | `Type<Value>` | Decoder applied to each token |
|
|
80
|
+
| `description` | `string?` | Help text |
|
|
81
|
+
| `hint` | `string?` | Short note in parentheses |
|
|
82
|
+
| `endDelimiter` | `string?` | Sentinel token that stops collection |
|
|
90
83
|
|
|
91
84
|
## Ordering rules
|
|
92
85
|
|
|
@@ -97,20 +90,17 @@ operation(
|
|
|
97
90
|
{
|
|
98
91
|
options: {},
|
|
99
92
|
positionals: [
|
|
100
|
-
positionalRequired({ type:
|
|
101
|
-
positionalRequired({ type:
|
|
102
|
-
positionalOptional({
|
|
103
|
-
|
|
104
|
-
label: "TAG",
|
|
105
|
-
default: () => "latest",
|
|
106
|
-
}),
|
|
107
|
-
positionalVariadics({ type: typeString, label: "EXTRA" }),
|
|
93
|
+
positionalRequired({ type: type("src") }),
|
|
94
|
+
positionalRequired({ type: type("dst") }),
|
|
95
|
+
positionalOptional({ type: type("tag"), default: () => "latest" }),
|
|
96
|
+
positionalVariadics({ type: type("extra") }),
|
|
108
97
|
],
|
|
109
98
|
},
|
|
110
|
-
async (_ctx, { positionals: [
|
|
99
|
+
async function (_ctx, { positionals: [src, dst, tag, extras] }) {
|
|
111
100
|
/* ... */
|
|
112
101
|
},
|
|
113
102
|
);
|
|
114
|
-
//
|
|
115
|
-
//
|
|
103
|
+
// Usage:
|
|
104
|
+
// my-cli in out → src="in", src="out", tag="latest", extras=[]
|
|
105
|
+
// my-cli in out v2 a b c → src="in", src="out", tag="v2", extras=["a","b","c"]
|
|
116
106
|
```
|
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
|
```
|