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.
@@ -1,79 +1,61 @@
1
1
  # Types
2
2
 
3
- A `Type<Value>` converts a raw CLI string into a typed value: a `content` label paired with a `decoder`.
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
- | Export | TypeScript type | Accepts |
8
- | ------------- | --------------- | ---------------------------------------------------------- |
9
- | `typeString` | `string` | Any string |
10
- | `typeBoolean` | `boolean` | `true`, `yes`, `false`, `no` (case-insensitive) |
11
- | `typeNumber` | `number` | Integers, floats, scientific notation |
12
- | `typeInteger` | `bigint` | Integer strings only |
13
- | `typeDate` | `Date` | Any format accepted by `Date.parse` (ISO 8601 recommended) |
14
- | `typeUrl` | `URL` | Absolute URLs |
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
- import {
18
- typeBoolean,
19
- typeDate,
20
- typeInteger,
21
- typeNumber,
22
- typeString,
23
- typeUrl,
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
- ## `typeOneOf` string enum
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
- import { typeOneOf } from "cli-kiss";
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
- ## `typeMapped` — transform an existing type
39
+ ## `typeChoice` — string enum
49
40
 
50
- Chain a `before` type with an `after` transformation:
41
+ Accepts only a fixed set of strings (case-insensitive by default):
51
42
 
52
43
  ```ts
53
- import { typeMapped, typeNumber } from "cli-kiss";
54
-
55
- const typePort = typeMapped(typeNumber, {
56
- content: "Port",
57
- decoder: (n) => {
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
- Errors from the `before` decoder are prefixed with `from: <content>`.
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
- import { typeTuple, typeNumber } from "cli-kiss";
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([typeString, typeNumber], ":");
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
- import { typeList, typeNumber } from "cli-kiss";
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(typeString, ":");
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: "HexColor",
122
+ content: "hex-color",
124
123
  decoder(value) {
125
- if (/^#[0-9a-fA-F]{6}$/.test(value)) return value;
126
- throw new Error(`Not a valid value: "${value}"`);
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
- // "--color #ff0000" → "#ff0000"
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
  ```
@@ -8,24 +8,24 @@
8
8
  await runAndExit(cliName, cliArgs, context, command, options?);
9
9
  ```
10
10
 
11
- | Parameter | Type | Description |
12
- | --------- | ---------------------------------- | ------------------------------------------------- |
13
- | `cliName` | `Lowercase<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 |
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 | 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
- | `useTtyColors` | `boolean \| "mock"?` | auto | Controls ANSI color output |
27
- | `onError` | `(error: unknown) => void` | — | Custom handler for execution errors |
28
- | `onExit` | `(code: number) => never` | `process.exit` | Override for testing |
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
- default: () => new URL("postgres://localhost/mydb"),
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 <SUBCOMMAND>
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 <URL> Database URL
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. Override:
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, { useTtyColors: true });
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, { useTtyColors: false });
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, { useTtyColors: "mock" });
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
- useTtyColors: false,
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/><span>K</span>eep
9
- <span>I</span>t <span>S</span>imple and <span>S</span>tupid, it just does the
10
- job.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cli-kiss",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "devDependencies": {
package/src/index.ts CHANGED
@@ -7,5 +7,3 @@ export * from "./lib/Run";
7
7
  export * from "./lib/Type";
8
8
  export * from "./lib/Typo";
9
9
  export * from "./lib/Usage";
10
-
11
- // TODO - maybe add a parsed option recap on error