cleye 2.4.0 → 2.6.0

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 CHANGED
@@ -342,6 +342,36 @@ const argv = cli({
342
342
  argv.flags.size // => "large" ("small" | "medium" | "large")
343
343
  ```
344
344
 
345
+ #### Composable type helpers
346
+
347
+ `cleye/formats` is a tree-shakable subpath that ships ready-made type-function helpers for common flag shapes. Import only what you need.
348
+
349
+ ```ts
350
+ import {
351
+ oneOf, commaList, integer, float, range, url
352
+ } from 'cleye/formats'
353
+
354
+ cli({
355
+ flags: {
356
+ format: { type: oneOf('json', 'yaml', 'csv') }, // => 'json' | 'yaml' | 'csv'
357
+ tags: { type: commaList(String) }, // => string[]
358
+ port: { type: range(1024, 65_535) }, // => number, validated in range
359
+ count: { type: integer() }, // => number (integer only)
360
+ ratio: { type: float() }, // => number (finite float)
361
+ apiUrl: { type: url() } // => URL object
362
+ }
363
+ })
364
+ ```
365
+
366
+ | Helper | Return type | Description |
367
+ |--------|-------------|-------------|
368
+ | `oneOf(...values)` | Union of the given string literals | Throws if the value is not in the list. |
369
+ | `commaList(itemType)` | `T[]` | Splits on `,`, trims whitespace, maps each item through `itemType`. |
370
+ | `integer()` | `number` | Parses a base-10 integer. Throws on floats or non-numeric input. |
371
+ | `float()` | `number` | Parses a finite float. Throws on non-finite or non-numeric input. |
372
+ | `range(min, max)` | `(input: string) => number` | Returns a parser that validates the input parses to a number in `[min, max]`. |
373
+ | `url()` | `URL` | Parses with `new URL()`. Returns a `URL` object so callers get `.host`, `.pathname`, etc. |
374
+
345
375
  #### Default flags
346
376
  By default, _Cleye_ will try to handle the `--help, -h` and `--version` flags.
347
377
 
@@ -366,6 +396,18 @@ $ my-script --version
366
396
 
367
397
  The version is also shown in the help documentation. To opt out of handling `--version` while still showing the version in `--help`, pass the version into `help.version`.
368
398
 
399
+ > [!TIP]
400
+ > Import `name`, `version`, and `description` directly from `package.json` to avoid keeping them in sync manually:
401
+ > ```ts
402
+ > import { name, version, description } from './package.json' with { type: 'json' }
403
+ >
404
+ > cli({
405
+ > name,
406
+ > version,
407
+ > help: { description }
408
+ > })
409
+ > ```
410
+
369
411
  #### Strict flags
370
412
  To reject unknown flags with an error, enable `strictFlags`:
371
413
 
@@ -0,0 +1 @@
1
+ "use strict";var i=Object.defineProperty;var n=(r,e)=>i(r,"name",{value:e,configurable:!0});const s=n((...r)=>e=>{if(!r.includes(e))throw new Error(`Expected one of: ${r.join(", ")} (got: ${JSON.stringify(e)})`);return e},"oneOf"),c=n(r=>e=>e===""?[]:e.split(",").flatMap(o=>{const t=o.trim();return t===""?[]:[r(t)]}),"commaList"),f=n(()=>r=>{const e=Number(r);if(!Number.isInteger(e))throw new TypeError(`Expected an integer (got: ${JSON.stringify(r)})`);return e},"integer"),a=n(()=>r=>{const e=Number(r);if(!Number.isFinite(e))throw new TypeError(`Expected a finite number (got: ${JSON.stringify(r)})`);return e},"float"),g=n((r,e)=>o=>{const t=Number(o);if(!Number.isFinite(t))throw new TypeError(`Expected a number (got: ${JSON.stringify(o)})`);if(t<r||t>e)throw new Error(`Expected a number between ${r} and ${e} (got: ${t})`);return t},"range"),u=n(()=>r=>{try{return new URL(r)}catch{throw new Error(`Expected a valid URL (got: ${JSON.stringify(r)})`)}},"url");exports.commaList=c,exports.float=a,exports.integer=f,exports.oneOf=s,exports.range=g,exports.url=u;
@@ -0,0 +1,8 @@
1
+ declare const oneOf: <T extends string>(...values: T[]) => (input: string) => T;
2
+ declare const commaList: <T>(itemType: (value: string) => T) => (input: string) => T[];
3
+ declare const integer: () => (input: string) => number;
4
+ declare const float: () => (input: string) => number;
5
+ declare const range: (min: number, max: number) => (input: string) => number;
6
+ declare const url: () => (input: string) => URL;
7
+
8
+ export { commaList, float, integer, oneOf, range, url };
@@ -0,0 +1,8 @@
1
+ declare const oneOf: <T extends string>(...values: T[]) => (input: string) => T;
2
+ declare const commaList: <T>(itemType: (value: string) => T) => (input: string) => T[];
3
+ declare const integer: () => (input: string) => number;
4
+ declare const float: () => (input: string) => number;
5
+ declare const range: (min: number, max: number) => (input: string) => number;
6
+ declare const url: () => (input: string) => URL;
7
+
8
+ export { commaList, float, integer, oneOf, range, url };
@@ -0,0 +1 @@
1
+ var i=Object.defineProperty;var n=(r,e)=>i(r,"name",{value:e,configurable:!0});const c=n((...r)=>e=>{if(!r.includes(e))throw new Error(`Expected one of: ${r.join(", ")} (got: ${JSON.stringify(e)})`);return e},"oneOf"),s=n(r=>e=>e===""?[]:e.split(",").flatMap(o=>{const t=o.trim();return t===""?[]:[r(t)]}),"commaList"),f=n(()=>r=>{const e=Number(r);if(!Number.isInteger(e))throw new TypeError(`Expected an integer (got: ${JSON.stringify(r)})`);return e},"integer"),g=n(()=>r=>{const e=Number(r);if(!Number.isFinite(e))throw new TypeError(`Expected a finite number (got: ${JSON.stringify(r)})`);return e},"float"),a=n((r,e)=>o=>{const t=Number(o);if(!Number.isFinite(t))throw new TypeError(`Expected a number (got: ${JSON.stringify(o)})`);if(t<r||t>e)throw new Error(`Expected a number between ${r} and ${e} (got: ${t})`);return t},"range"),u=n(()=>r=>{try{return new URL(r)}catch{throw new Error(`Expected a valid URL (got: ${JSON.stringify(r)})`)}},"url");export{s as commaList,g as float,f as integer,c as oneOf,a as range,u as url};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cleye",
3
- "version": "2.4.0",
3
+ "version": "2.6.0",
4
4
  "description": "The intuitive CLI development tool",
5
5
  "keywords": [
6
6
  "cli",
@@ -19,20 +19,34 @@
19
19
  "email": "hiroki.osame@gmail.com"
20
20
  },
21
21
  "files": [
22
- "dist"
22
+ "dist",
23
+ "skills"
23
24
  ],
24
25
  "type": "module",
26
+ "sideEffects": false,
25
27
  "main": "./dist/index.cjs",
26
28
  "module": "./dist/index.mjs",
27
29
  "types": "./dist/index.d.cts",
28
30
  "exports": {
29
- "require": {
30
- "types": "./dist/index.d.cts",
31
- "default": "./dist/index.cjs"
31
+ ".": {
32
+ "require": {
33
+ "types": "./dist/index.d.cts",
34
+ "default": "./dist/index.cjs"
35
+ },
36
+ "import": {
37
+ "types": "./dist/index.d.mts",
38
+ "default": "./dist/index.mjs"
39
+ }
32
40
  },
33
- "import": {
34
- "types": "./dist/index.d.mts",
35
- "default": "./dist/index.mjs"
41
+ "./formats": {
42
+ "require": {
43
+ "types": "./dist/formats.d.cts",
44
+ "default": "./dist/formats.cjs"
45
+ },
46
+ "import": {
47
+ "types": "./dist/formats.d.mts",
48
+ "default": "./dist/formats.mjs"
49
+ }
36
50
  }
37
51
  },
38
52
  "imports": {
@@ -40,6 +54,11 @@
40
54
  "types": "./src/index.ts",
41
55
  "development": "./src/index.ts",
42
56
  "default": "./dist/index.mjs"
57
+ },
58
+ "#cleye/formats": {
59
+ "types": "./src/formats.ts",
60
+ "development": "./src/formats.ts",
61
+ "default": "./dist/formats.mjs"
43
62
  }
44
63
  },
45
64
  "dependencies": {
@@ -0,0 +1,292 @@
1
+ ---
2
+ name: cleye
3
+ description: cleye CLI development tool — argv parsing with typed parameters, flags, commands, and auto-generated help. Use when building Node.js CLI scripts with cleye or when the user imports `cli` or `command` from `cleye`.
4
+ ---
5
+
6
+ # cleye
7
+
8
+ ## Quick Patterns
9
+
10
+ | Task | Pattern |
11
+ |------|---------|
12
+ | Basic CLI | `cli({ name, parameters, flags })` |
13
+ | Named command | `command({ name, parameters, flags }, callback)` |
14
+ | Register commands | `cli({ commands: [cmd1, cmd2] })` |
15
+ | Async callback | `await cli({ ... }, async (argv) => { ... })` |
16
+ | Raw argv | `cli({ ... }, callback, process.argv.slice(2))` |
17
+
18
+ ## Parameters
19
+
20
+ Positional arguments mapped to named properties on `argv._`.
21
+
22
+ | Format | Description |
23
+ |--------|-------------|
24
+ | `<name>` | Required |
25
+ | `[name]` | Optional |
26
+ | `<name...>` | Required spread (1+) |
27
+ | `[name...]` | Optional spread (0+) |
28
+ | `--` | End-of-flags separator |
29
+
30
+ ```ts
31
+ const argv = cli({
32
+ parameters: ['<required>', '[optional]', '[rest...]']
33
+ })
34
+ argv._.required // string
35
+ argv._.optional // string | undefined
36
+ argv._.rest // string[]
37
+ argv._['--'] // string[] — everything after --
38
+ ```
39
+
40
+ Multi-word names use camelCase on `argv._`: `'<first name>'` → `argv._.firstName`.
41
+
42
+ ## Flags
43
+
44
+ ```ts
45
+ const argv = cli({
46
+ flags: {
47
+ verbose: Boolean, // shorthand
48
+ output: {
49
+ type: String,
50
+ alias: 'o',
51
+ default: 'dist',
52
+ description: 'Output directory',
53
+ placeholder: '<path>'
54
+ },
55
+ count: {
56
+ type: [Number], // array: -n 1 -n 2
57
+ alias: 'n'
58
+ }
59
+ }
60
+ })
61
+
62
+ argv.flags.verbose // boolean | undefined
63
+ argv.flags.output // string
64
+ argv.flags.count // number[]
65
+ ```
66
+
67
+ Flag name is camelCase; parsed from kebab-case CLI input (`--output-dir` → `outputDir`).
68
+
69
+ **Delimiters** — `=`, `:`, and `.` all work as value separators:
70
+
71
+ ```sh
72
+ --flag=value --flag:value --flag.value
73
+ ```
74
+
75
+ Use `:` when the value itself contains `=` (e.g. `--define:KEY=VALUE`).
76
+
77
+ **Counting flags** — use `[Boolean]` and check `.length`:
78
+
79
+ ```ts
80
+ cli({
81
+ flags: {
82
+ verbose: {
83
+ type: [Boolean],
84
+ alias: 'v'
85
+ }
86
+ }
87
+ })
88
+ // -vvv → argv.flags.verbose.length === 3
89
+ ```
90
+
91
+ **Optional value** — custom type returns `true` when no value is given:
92
+
93
+ ```ts
94
+ const OptionalString = (value: string) => value || true
95
+ cli({ flags: { tag: OptionalString } })
96
+ // --tag → true
97
+ // --tag beta → 'beta'
98
+ ```
99
+
100
+ ## Boolean Flag Negation
101
+
102
+ By default, set a boolean flag to `false` using the `=` operator:
103
+
104
+ ```sh
105
+ --verbose=false # → false
106
+ --verbose false # → true (false treated as separate argument)
107
+ ```
108
+
109
+ To also support the `--no-<flag>` convention, enable `booleanFlagNegation` (additive — `=false` still works):
110
+
111
+ ```ts
112
+ cli({
113
+ flags: { verbose: Boolean },
114
+ booleanFlagNegation: true
115
+ })
116
+ // --no-verbose → false
117
+ // --verbose=false → false (still works)
118
+ // Last-wins: --verbose --no-verbose → false
119
+ ```
120
+
121
+ Commands inherit `booleanFlagNegation` from the parent `cli()` but can override it.
122
+
123
+ ## Strict Flags
124
+
125
+ ```ts
126
+ cli({
127
+ flags: { foo: Boolean },
128
+ strictFlags: true
129
+ })
130
+ // --baz → Error: Unknown flag: --baz. (Did you mean --foo?)
131
+ ```
132
+
133
+ Commands inherit `strictFlags` but can override it.
134
+
135
+ ## Commands
136
+
137
+ ```ts
138
+ // install-command.ts
139
+ export const installCommand = command({
140
+ name: 'install',
141
+ alias: ['i', 'add'],
142
+ parameters: ['<package name>'],
143
+ flags: { saveDev: Boolean }
144
+ }, (argv) => {
145
+ argv._.packageName // string
146
+ argv.flags.saveDev // boolean | undefined
147
+ })
148
+
149
+ // main.ts
150
+ cli({
151
+ name: 'npm',
152
+ version: '1.0.0',
153
+ commands: [installCommand]
154
+ })
155
+ ```
156
+
157
+ When a command is matched, `argv.command` narrows the type for type-safe branching.
158
+
159
+ ## Help & Version
160
+
161
+ ```ts
162
+ cli({
163
+ name: 'my-script',
164
+ version: '1.2.3', // enables --version; also shown in --help
165
+ help: {
166
+ description: 'Does things',
167
+ usage: 'my-script [flags] <file>',
168
+ examples: ['my-script foo.txt', 'my-script --output=dist foo.txt'],
169
+ version: '1.2.3' // show version in --help without handling --version
170
+ }
171
+ })
172
+ // help: false — disables --help handling; call argv.showHelp() manually
173
+ ```
174
+
175
+ Tip: import `name`, `version`, and `description` from `package.json` to avoid keeping them in sync manually:
176
+
177
+ ```ts
178
+ import { name, version, description } from './package.json' with { type: 'json' }
179
+
180
+ cli({
181
+ name,
182
+ version,
183
+ help: { description }
184
+ })
185
+ ```
186
+
187
+ ## Custom Flag Types
188
+
189
+ Any function `(value: string) => T` works as a flag type — the return type is inferred.
190
+
191
+ ```ts
192
+ // Validation + narrowing
193
+ const Port = (value: string) => {
194
+ const n = Number(value)
195
+ if (!Number.isInteger(n) || n < 1 || n > 65_535) { throw new Error(`Invalid port: ${value}`) }
196
+ return n
197
+ }
198
+ // argv.flags.port → number
199
+
200
+ // Enum
201
+ const sizes = ['small', 'medium', 'large'] as const
202
+ const Size = (v: string) => {
203
+ if (!(sizes as readonly string[]).includes(v)) { throw new Error(`Expected: ${sizes.join(', ')}`) }
204
+ return v as typeof sizes[number]
205
+ }
206
+ // argv.flags.size → 'small' | 'medium' | 'large'
207
+
208
+ // Comma-separated list
209
+ const List = (v: string) => v.split(',')
210
+ // --tags a,b,c → argv.flags.tags === ['a', 'b', 'c']
211
+
212
+ // Inline JSON
213
+ cli({ flags: { data: JSON.parse } })
214
+ // --data '{"key":1}' → argv.flags.data === { key: 1 }
215
+
216
+ // Dot-nested object (combine with . delimiter)
217
+ const Env = (v: string): Record<string, string | true> => {
218
+ const [k, value] = v.split('=')
219
+ return { [k]: value ?? true }
220
+ }
221
+ cli({ flags: { env: [Env] } })
222
+ // --env.TOKEN=abc --env.CI → merge to { TOKEN: 'abc', CI: true }
223
+ ```
224
+
225
+ ## Type Helpers (`cleye/formats`)
226
+
227
+ `cleye/formats` ships composable, tree-shakable type-function helpers. Import only what you need.
228
+
229
+ ```ts
230
+ import {
231
+ oneOf, commaList, integer, float, range, url
232
+ } from 'cleye/formats'
233
+ ```
234
+
235
+ - `oneOf('a', 'b', 'c')` — infers `'a' | 'b' | 'c'`; throws if the value is not in the list.
236
+ - `commaList(itemType)` — splits `"a,b,c"` → `T[]`; trims whitespace; composes with other helpers (e.g. `commaList(integer())`).
237
+ - `integer()` — base-10 integer; throws on floats/non-numeric.
238
+ - `float()` — finite float; throws on `Infinity`/non-numeric.
239
+ - `range(min, max)` — returns `(input: string) => number`; validates input parses to a number in `[min, max]`.
240
+ - `url()` — parses with `new URL()`; returns a `URL` object (not a string).
241
+
242
+ ## Help Customization
243
+
244
+ ```ts
245
+ cli({
246
+ help: {
247
+ render(nodes, renderers) {
248
+ nodes.push('\nDocs: https://example.com')
249
+ renderers.flagOperator = () => '=' // --flag=<value>
250
+ return renderers.render(nodes)
251
+ }
252
+ }
253
+ })
254
+ ```
255
+
256
+ Default renderers: `/src/render-help/renderers.ts`.
257
+
258
+ ## ignoreArgv
259
+
260
+ ```ts
261
+ cli({
262
+ ignoreArgv(type, flagOrArgv, value) {
263
+ if (type === 'unknown-flag') { return true } // silently skip unknown flags
264
+ }
265
+ })
266
+ ```
267
+
268
+ `type`: `'known-flag' | 'unknown-flag' | 'argument'`
269
+
270
+ ## Return Type
271
+
272
+ ```ts
273
+ type ParsedArgv = {
274
+ _: string[] & Parameters
275
+ flags: { [name: string]: InferredType }
276
+ unknownFlags: { [name: string]: (string | boolean)[] }
277
+ showVersion: () => void
278
+ showHelp: (options?: HelpOptions) => void
279
+ }
280
+ ```
281
+
282
+ ## TypeScript Exports
283
+
284
+ ```ts
285
+ import type { Flags, Renderers, TypeFlag } from 'cleye'
286
+ ```
287
+
288
+ | Type | Use |
289
+ |------|-----|
290
+ | `Flags` | Type for the `flags` option object |
291
+ | `Renderers` | Help renderer class type for `help.render` customization |
292
+ | `TypeFlag` | Re-export from `type-flag` for portable type declarations |