icore 1.0.6 → 1.0.8

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.
Files changed (3) hide show
  1. package/LICENSE +1 -1
  2. package/package.json +2 -2
  3. package/readme.md +100 -97
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2017 Stanislav Woodger
3
+ Copyright (c) 2017 Stanislav Potemkin
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "icore",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "Declarative command line interface mechanics for Node.js",
5
5
  "keywords": [
6
6
  "command-line",
@@ -14,7 +14,7 @@
14
14
  ],
15
15
  "license": "MIT",
16
16
  "author": {
17
- "name": "Stanislav Woodger",
17
+ "name": "Stanislav Potemkin",
18
18
  "email": "woodger@ya.ru"
19
19
  },
20
20
  "repository": {
package/readme.md CHANGED
@@ -7,7 +7,14 @@
7
7
 
8
8
  Small dependency-free command line interface mechanics for [Node.js®](https://nodejs.org) applications.
9
9
 
10
- ### Installation
10
+ Supports a practical GNU-style option syntax:
11
+
12
+ - long options: `--name value`, `--name=value`;
13
+ - boolean flags: `--flag`, `--flag=true`, `--flag=false`, `--no-flag`;
14
+ - short aliases: `-f`, `-n value`;
15
+ - option terminator: `--`.
16
+
17
+ ## Installation
11
18
 
12
19
  To use `icore` in your project, run:
13
20
 
@@ -15,29 +22,32 @@ To use `icore` in your project, run:
15
22
  npm install icore
16
23
  ```
17
24
 
18
- ### Table of Contents
19
-
20
- * [API](#api)
21
- * [`parseArgv(args, schema?)`](#parseargvargs-schema)
22
- * [`parseOptions(schema, values)`](#parseoptionsschema-values)
23
- * [`parseOptionsDetailed(schema, values)`](#parseoptionsdetailedschema-values)
24
- * [`defineCommand(command)`](#definecommandcommand)
25
- * [`defineCommandRegistry(commands)`](#definecommandregistrycommands)
26
- * [`isCommandName(registry, value)`](#iscommandnameregistry-value)
27
- * [`resolveCommand(registry, positionals)`](#resolvecommandregistry-positionals)
28
- * [`resolveCommandFromArgs(registry, args)`](#resolvecommandfromargsregistry-args)
29
- * [`runCommandFromRegistry(registry, args, context)`](#runcommandfromregistryregistry-args-context)
30
- * [`mergeOptionsSchema(...schemas)`](#mergeoptionsschemaschemas)
31
- * [`runCommand(command, args, context)`](#runcommandcommand-args-context)
32
- * [Example](#example)
33
- * [Option Schemas](#option-schemas)
34
- * [Type Inference](#type-inference)
35
- * [Facade of arguments](#facade-of-arguments)
36
- * [Error Messages](#error-messages)
37
-
38
- ### API
39
-
40
- #### `parseArgv(args, schema?)`
25
+ ## Table of Contents
26
+
27
+ - [Installation](#installation)
28
+ - [API](#api)
29
+ - [`parseArgv(args, schema?)`](#parseargvargs-schema)
30
+ - [`parseOptions(schema, values)`](#parseoptionsschema-values)
31
+ - [`parseOptionsDetailed(schema, values)`](#parseoptionsdetailedschema-values)
32
+ - [`defineCommand(command)`](#definecommandcommand)
33
+ - [`defineCommandRegistry(commands)`](#definecommandregistrycommands)
34
+ - [`isCommandName(registry, value)`](#iscommandnameregistry-value)
35
+ - [`resolveCommand(registry, positionals)`](#resolvecommandregistry-positionals)
36
+ - [`resolveCommandFromArgs(registry, args)`](#resolvecommandfromargsregistry-args)
37
+ - [`runCommandFromRegistry(registry, args, context)`](#runcommandfromregistryregistry-args-context)
38
+ - [`mergeOptionsSchema(...schemas)`](#mergeoptionsschemaschemas)
39
+ - [`runCommand(command, args, context)`](#runcommandcommand-args-context)
40
+ - [How It Works](#how-it-works)
41
+ - [Example](#example)
42
+ - [Option Schemas](#option-schemas)
43
+ - [Type Inference](#type-inference)
44
+ - [Facade of arguments](#facade-of-arguments)
45
+ - [Error Messages](#error-messages)
46
+ - [Project Boundary](#project-boundary)
47
+
48
+ ## API
49
+
50
+ ### `parseArgv(args, schema?)`
41
51
 
42
52
  Parses raw CLI arguments into positionals and raw option values.
43
53
 
@@ -47,8 +57,8 @@ import { parseArgv } from 'icore';
47
57
  const argv = parseArgv([
48
58
  'hello',
49
59
  '--name',
50
- 'User',
51
- '--upper'
60
+ 'Alice',
61
+ '--uppercase'
52
62
  ]);
53
63
  ```
54
64
 
@@ -58,8 +68,8 @@ Result:
58
68
  {
59
69
  positionals: ['hello'],
60
70
  options: {
61
- name: 'User',
62
- upper: true
71
+ name: 'Alice',
72
+ uppercase: true
63
73
  }
64
74
  }
65
75
  ```
@@ -70,10 +80,10 @@ options without consuming the following positional argument:
70
80
  ```ts
71
81
  const argv = parseArgv([
72
82
  'hello',
73
- '--upper',
74
- 'User'
83
+ '--uppercase',
84
+ 'Alice'
75
85
  ], {
76
- upper: {
86
+ uppercase: {
77
87
  type: 'boolean'
78
88
  }
79
89
  });
@@ -83,14 +93,14 @@ Result:
83
93
 
84
94
  ```ts
85
95
  {
86
- positionals: ['hello', 'User'],
96
+ positionals: ['hello', 'Alice'],
87
97
  options: {
88
- upper: true
98
+ uppercase: true
89
99
  }
90
100
  }
91
101
  ```
92
102
 
93
- #### `parseOptions(schema, values)`
103
+ ### `parseOptions(schema, values)`
94
104
 
95
105
  Validates raw option values using an option schema and returns typed options.
96
106
 
@@ -102,12 +112,12 @@ const options = parseOptions({
102
112
  type: 'string',
103
113
  default: 'world'
104
114
  },
105
- upper: {
115
+ uppercase: {
106
116
  type: 'boolean'
107
117
  }
108
118
  } as const, {
109
- name: 'User',
110
- upper: true
119
+ name: 'Alice',
120
+ uppercase: true
111
121
  });
112
122
  ```
113
123
 
@@ -115,12 +125,12 @@ Result:
115
125
 
116
126
  ```ts
117
127
  {
118
- name: 'User',
119
- upper: true
128
+ name: 'Alice',
129
+ uppercase: true
120
130
  }
121
131
  ```
122
132
 
123
- #### `parseOptionsDetailed(schema, values)`
133
+ ### `parseOptionsDetailed(schema, values)`
124
134
 
125
135
  Validates raw option values and returns parsed options together with
126
136
  user-provided metadata.
@@ -133,11 +143,11 @@ const result = parseOptionsDetailed({
133
143
  type: 'string',
134
144
  default: 'world'
135
145
  },
136
- upper: {
146
+ uppercase: {
137
147
  type: 'boolean'
138
148
  }
139
149
  } as const, {
140
- upper: true
150
+ uppercase: true
141
151
  });
142
152
  ```
143
153
 
@@ -147,11 +157,11 @@ Result:
147
157
  {
148
158
  options: {
149
159
  name: 'world',
150
- upper: true
160
+ uppercase: true
151
161
  },
152
162
  provided: {
153
163
  name: false,
154
- upper: true
164
+ uppercase: true
155
165
  }
156
166
  }
157
167
  ```
@@ -159,7 +169,7 @@ Result:
159
169
  `provided` is useful when a default value and an omitted option have different
160
170
  application-level meaning.
161
171
 
162
- #### `defineCommand(command)`
172
+ ### `defineCommand(command)`
163
173
 
164
174
  Defines a command while preserving its option schema types.
165
175
 
@@ -173,19 +183,19 @@ const command = defineCommand({
173
183
  type: 'string',
174
184
  default: 'world'
175
185
  },
176
- upper: {
186
+ uppercase: {
177
187
  type: 'boolean'
178
188
  }
179
189
  },
180
190
  async handle({ options }) {
181
191
  const message = `Hello, ${options.name}!`;
182
192
 
183
- return options.upper ? message.toUpperCase() : message;
193
+ return options.uppercase ? message.toUpperCase() : message;
184
194
  }
185
195
  });
186
196
  ```
187
197
 
188
- #### `defineCommandRegistry(commands)`
198
+ ### `defineCommandRegistry(commands)`
189
199
 
190
200
  Defines a command registry while preserving literal command path types.
191
201
 
@@ -215,7 +225,7 @@ Result:
215
225
 
216
226
  Duplicate command paths are rejected.
217
227
 
218
- #### `isCommandName(registry, value)`
228
+ ### `isCommandName(registry, value)`
219
229
 
220
230
  Checks whether an unknown value is a command name registered in the registry.
221
231
 
@@ -225,7 +235,7 @@ if (isCommandName(registry, value)) {
225
235
  }
226
236
  ```
227
237
 
228
- #### `resolveCommand(registry, positionals)`
238
+ ### `resolveCommand(registry, positionals)`
229
239
 
230
240
  Resolves a command from already parsed positional arguments.
231
241
 
@@ -235,7 +245,7 @@ import { resolveCommand } from 'icore';
235
245
  const resolved = resolveCommand(registry, [
236
246
  'hello',
237
247
  'formal',
238
- 'User'
248
+ 'Alice'
239
249
  ]);
240
250
  ```
241
251
 
@@ -246,14 +256,14 @@ Result:
246
256
  name: 'hello formal',
247
257
  path: ['hello', 'formal'],
248
258
  command: helloFormalCommand,
249
- positionals: ['User']
259
+ positionals: ['Alice']
250
260
  }
251
261
  ```
252
262
 
253
263
  When several command paths match, the most specific command wins. For example,
254
264
  `hello formal` is preferred over `hello`.
255
265
 
256
- #### `resolveCommandFromArgs(registry, args)`
266
+ ### `resolveCommandFromArgs(registry, args)`
257
267
 
258
268
  Resolves a command from raw CLI arguments. Each candidate command is parsed with
259
269
  its own option schema, so boolean flags do not accidentally consume command path
@@ -263,19 +273,19 @@ segments.
263
273
  const resolved = resolveCommandFromArgs(registry, [
264
274
  'hello',
265
275
  '--name',
266
- 'User',
267
- '--upper'
276
+ 'Alice',
277
+ '--uppercase'
268
278
  ]);
269
279
  ```
270
280
 
271
- #### `runCommandFromRegistry(registry, args, context)`
281
+ ### `runCommandFromRegistry(registry, args, context)`
272
282
 
273
283
  Resolves a command from a registry and runs its handler.
274
284
 
275
285
  ```ts
276
286
  const output = await runCommandFromRegistry(
277
287
  registry,
278
- ['hello', '--name', 'User', '--upper'],
288
+ ['hello', '--name', 'Alice', '--uppercase'],
279
289
  context
280
290
  );
281
291
  ```
@@ -283,7 +293,7 @@ const output = await runCommandFromRegistry(
283
293
  This is **registry-level mechanics only**. Application-specific setup, side
284
294
  effects, and output formatting still belong outside `icore`.
285
295
 
286
- #### `mergeOptionsSchema(...schemas)`
296
+ ### `mergeOptionsSchema(...schemas)`
287
297
 
288
298
  Merges multiple option schemas while preserving literal option definition types.
289
299
  Later schemas override earlier schemas with the same option name.
@@ -299,7 +309,7 @@ const nameOptions = {
299
309
  } as const;
300
310
 
301
311
  const greetingOptions = {
302
- upper: {
312
+ uppercase: {
303
313
  type: 'boolean'
304
314
  }
305
315
  } as const;
@@ -307,7 +317,7 @@ const greetingOptions = {
307
317
  const options = mergeOptionsSchema(nameOptions, greetingOptions);
308
318
  ```
309
319
 
310
- #### `runCommand(command, args, context)`
320
+ ### `runCommand(command, args, context)`
311
321
 
312
322
  Parses arguments, validates options, checks command positionals, and runs the
313
323
  handler.
@@ -315,7 +325,7 @@ handler.
315
325
  ```ts
316
326
  const output = await runCommand(
317
327
  command,
318
- ['hello', '--name', 'User', '--upper'],
328
+ ['hello', '--name', 'Alice', '--uppercase'],
319
329
  context
320
330
  );
321
331
  ```
@@ -323,12 +333,12 @@ const output = await runCommand(
323
333
  **By default**, extra positionals are rejected. A command can opt in to extra
324
334
  positionals with `allowExtraPositionals: true`.
325
335
 
326
- ### How It Works
336
+ ## How It Works
327
337
 
328
- ![yuml diagram](http://yuml.me/diagram/scruffy;dir:LR;/class/[*argv*%20{bg:gray}|External;hello%20--name%20User%20--upper]->[*matches*%20{bg:lavender}|System;parse,%20resolve,%20validate,%20infer]->[*typed%20result*%20{bg:honeydew}|Container;command=hello;%20name=User;%20upper=true]->[*your%20app*%20{bg:cornsilk}|System;business%20logic%20and%20output])
338
+ ![yuml diagram](http://yuml.me/diagram/scruffy;dir:LR;/class/[*argv*%20{bg:gray}|External;hello%20--name%20Alice%20--uppercase]->[*matches*%20{bg:lavender}|System;parse,%20resolve,%20validate,%20infer]->[*typed%20result*%20{bg:honeydew}|Container;command=hello;%20name=Alice;%20uppercase=true]->[*your%20app*%20{bg:cornsilk}|System;business%20logic%20and%20output])
329
339
 
330
340
 
331
- ### Example
341
+ ## Example
332
342
 
333
343
  ```ts
334
344
  import { defineCommand, runCommand } from 'icore';
@@ -340,20 +350,20 @@ const exampleCommand = defineCommand({
340
350
  type: 'string',
341
351
  default: 'world'
342
352
  },
343
- upper: {
353
+ uppercase: {
344
354
  type: 'boolean'
345
355
  }
346
356
  },
347
357
  async handle({ options }) {
348
358
  const message = `Hello, ${options.name}!`;
349
359
 
350
- return options.upper ? message.toUpperCase() : message;
360
+ return options.uppercase ? message.toUpperCase() : message;
351
361
  }
352
362
  });
353
363
 
354
364
  const output = await runCommand(
355
365
  exampleCommand,
356
- ['hello', '--name', 'User', '--upper'],
366
+ ['hello', '--name', 'Alice', '--uppercase'],
357
367
  {}
358
368
  );
359
369
 
@@ -363,14 +373,14 @@ console.log(output);
363
373
  Terminal output:
364
374
 
365
375
  ```console
366
- $ node cli.js hello --name User --upper
367
- HELLO, USER!
376
+ $ node cli.js hello --name Alice --uppercase
377
+ HELLO, ALICE!
368
378
  ```
369
379
 
370
380
  The command handler receives parsed options, user-provided option metadata,
371
381
  remaining positionals, and caller provided context.
372
382
 
373
- ### Option Schemas
383
+ ## Option Schemas
374
384
 
375
385
  Options are described as plain objects.
376
386
 
@@ -386,7 +396,7 @@ const schema = {
386
396
  type: 'string',
387
397
  alias: 'n'
388
398
  },
389
- upper: {
399
+ uppercase: {
390
400
  type: 'boolean',
391
401
  alias: 'u'
392
402
  }
@@ -396,7 +406,7 @@ const schema = {
396
406
  Aliases must be a single ASCII letter and unique within the schema. Parsed
397
407
  values are always returned by long option name.
398
408
 
399
- #### `type: 'string'`
409
+ ### `type: 'string'`
400
410
 
401
411
  ```ts
402
412
  const schema = {
@@ -415,11 +425,11 @@ const schema = {
415
425
  String options reject missing required values, blank strings such as `--name=`,
416
426
  boolean flag form, and values outside `choices`.
417
427
 
418
- #### `type: 'boolean'`
428
+ ### `type: 'boolean'`
419
429
 
420
430
  ```ts
421
431
  const schema = {
422
- upper: {
432
+ uppercase: {
423
433
  type: 'boolean'
424
434
  }
425
435
  } as const;
@@ -429,23 +439,23 @@ Boolean options accept **flag form**, explicit `true` / `false` values, and
429
439
  schema-known negation:
430
440
 
431
441
  ```sh
432
- --upper
433
- --upper=true
434
- --upper=false
435
- --no-upper
442
+ --uppercase
443
+ --uppercase=true
444
+ --uppercase=false
445
+ --no-uppercase
436
446
  ```
437
447
 
438
448
  Invalid explicit values are rejected:
439
449
 
440
450
  ```sh
441
- --upper=yes
442
- --upper=
451
+ --uppercase=yes
452
+ --uppercase=
443
453
  ```
444
454
 
445
- `--upper false` keeps `--upper` as `true` and leaves `false` as a positional
455
+ `--uppercase false` keeps `--uppercase` as `true` and leaves `false` as a positional
446
456
  argument.
447
457
 
448
- #### `type: 'number'`
458
+ ### `type: 'number'`
449
459
 
450
460
  ```ts
451
461
  const schema = {
@@ -462,7 +472,7 @@ const schema = {
462
472
  Number options parse decimal numeric values and can validate integer and range
463
473
  constraints. Defaults are validated with the same rules as user-provided values.
464
474
 
465
- ### Type Inference
475
+ ## Type Inference
466
476
 
467
477
  Use `InferOptions` when you need the parsed option type explicitly.
468
478
 
@@ -474,7 +484,7 @@ const schema = {
474
484
  type: 'string',
475
485
  default: 'world'
476
486
  },
477
- upper: {
487
+ uppercase: {
478
488
  type: 'boolean'
479
489
  }
480
490
  } as const;
@@ -487,7 +497,7 @@ type Options = InferOptions<typeof schema>;
487
497
  ```ts
488
498
  type Options = {
489
499
  name: string;
490
- upper: boolean | undefined;
500
+ uppercase: boolean | undefined;
491
501
  };
492
502
  ```
493
503
 
@@ -527,14 +537,7 @@ type Name = CommandName<typeof helloFormalCommand>;
527
537
  type Name = 'hello formal';
528
538
  ```
529
539
 
530
- ### Facade of arguments
531
-
532
- Supports a practical GNU-style option syntax:
533
-
534
- - long options: `--name value`, `--name=value`;
535
- - boolean flags: `--flag`, `--flag=true`, `--flag=false`, `--no-flag`;
536
- - short aliases: `-f`, `-n value`;
537
- - option terminator: `--`.
540
+ ## Facade of arguments
538
541
 
539
542
  Use `--` to stop option parsing. The terminator itself is not included in
540
543
  positionals; every following token is treated as positional, even when it starts
@@ -552,7 +555,7 @@ Negated syntax such as `--no-cache` is interpreted as `cache: false` when
552
555
  `cache` is a known boolean option. Unknown negated options and negation for
553
556
  string or number options are rejected.
554
557
 
555
- ### Error Messages
558
+ ## Error Messages
556
559
 
557
560
  `icore` throws regular `Error` objects with predictable user-facing messages.
558
561
  Applications should treat these messages as **display text**, not as a
@@ -565,14 +568,14 @@ after printing `error.message`, terminal output can look like this:
565
568
  $ node cli.js hello --unknown
566
569
  Unexpected argument '--unknown'
567
570
 
568
- $ node cli.js hello --upper=yes
569
- Expected '--upper' as boolean flag
571
+ $ node cli.js hello --uppercase=yes
572
+ Expected '--uppercase' as boolean flag
570
573
 
571
574
  $ node cli.js hello --name=
572
575
  Expected '--name' as string
573
576
  ```
574
577
 
575
- ### Project Boundary
578
+ ## Project Boundary
576
579
 
577
580
  `icore` is intended to be a **small CLI mechanics module**. It should **not**
578
581
  grow into a domain-specific framework for a particular SDK or API.