icore 1.0.19 → 2.0.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/CHANGELOG.md +91 -54
- package/dist/argv/parser.js +1 -0
- package/dist/command/mechanics.js +3 -0
- package/dist/errors/icore-error.d.ts +133 -10
- package/dist/errors/icore-error.js +24 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -1
- package/dist/options/parser.js +7 -1
- package/dist/terminal/app.d.ts +31 -0
- package/dist/terminal/app.js +75 -26
- package/examples/terminal-app.md +67 -0
- package/package.json +2 -2
- package/readme.md +170 -640
package/readme.md
CHANGED
|
@@ -5,713 +5,243 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/icore)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
|
|
8
|
-
Small dependency-free command line interface and terminal presentation
|
|
8
|
+
Small dependency-free command line interface and terminal presentation
|
|
9
|
+
mechanics for [Node.js®](https://nodejs.org) applications.
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
`icore` owns the path from `process.argv` to terminal output:
|
|
11
12
|
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
13
|
+
- declarative, type-inferred option schemas;
|
|
14
|
+
- multi-segment command resolution;
|
|
15
|
+
- separate prepare and execute phases;
|
|
16
|
+
- JSON, CSV, and text-table presentation;
|
|
17
|
+
- stdout/stderr writers and reusable terminal error policy.
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
Application-specific API calls, configuration, domain behavior, and help text
|
|
20
|
+
remain in the consuming application.
|
|
18
21
|
|
|
19
|
-
|
|
22
|
+
## Requirements And Installation
|
|
23
|
+
|
|
24
|
+
`icore` requires Node.js `>=16.9.0` and includes TypeScript declarations.
|
|
20
25
|
|
|
21
26
|
```sh
|
|
22
27
|
npm install icore
|
|
23
28
|
```
|
|
24
29
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
- [API Reference](#api-reference)
|
|
28
|
-
- [`createTerminalApp()`](#createterminalapp)
|
|
29
|
-
- [`createCommand()`](#createcommand)
|
|
30
|
-
- [`command.define(command)`](#commanddefinecommand)
|
|
31
|
-
- [`command.registry(commands)`](#commandregistrycommands)
|
|
32
|
-
- [`command.run(command, args, context)`](#commandruncommand-args-context)
|
|
33
|
-
- [`createCommands(commands)`](#createcommandscommands)
|
|
34
|
-
- [`commands.prepare(args, options?)`](#commandsprepareargs-options)
|
|
35
|
-
- [`commands.run(prepared, context)`](#commandsrunprepared-context)
|
|
36
|
-
- [`commands.runFromArgs(args, context, options?)`](#commandsrunfromargsargs-context-options)
|
|
37
|
-
- [`createPresentation()`](#createpresentation)
|
|
38
|
-
- [`presentation.empty()`](#presentationempty)
|
|
39
|
-
- [`presentation.text(value)`](#presentationtextvalue)
|
|
40
|
-
- [`presentation.record(value)`](#presentationrecordvalue)
|
|
41
|
-
- [`presentation.records(values)`](#presentationrecordsvalues)
|
|
42
|
-
- [`presentation.table(rows)`](#presentationtablerows)
|
|
43
|
-
- [`presentation.csv(rows)`](#presentationcsvrows)
|
|
44
|
-
- [`presentation.render(result, format?)`](#presentationrenderresult-format)
|
|
45
|
-
- [`presentation.renderers.*`](#presentationrenderers)
|
|
46
|
-
- [`createOutput()`](#createoutput)
|
|
47
|
-
- [`output.write(chunk)`](#outputwritechunk)
|
|
48
|
-
- [`output.error(chunk)`](#outputerrorchunk)
|
|
49
|
-
- [`output.stdout.write(chunk)`](#outputstdoutwritechunk)
|
|
50
|
-
- [`output.stderr.write(chunk)`](#outputstderrwritechunk)
|
|
51
|
-
- [Lower-Level Mechanics](#lower-level-mechanics)
|
|
52
|
-
- [`parseArgv(args, schema?)`](#parseargv)
|
|
53
|
-
- [`parseOptionsDetailed(schema, values)`](#parseoptionsdetailed)
|
|
54
|
-
- [`resolveCommand(registry, positionals)`](#resolvecommand)
|
|
55
|
-
- [`resolveCommandFromArgs(registry, args)`](#resolvecommandfromargs)
|
|
56
|
-
- [`prepareCommandFromArgs(registry, args, options?)`](#preparecommandfromargs)
|
|
57
|
-
- [`runPreparedCommand(prepared, context)`](#runpreparedcommand)
|
|
58
|
-
- [`runCommandFromRegistry(registry, args, context, options?)`](#runcommandfromregistry)
|
|
59
|
-
- [`runCommand(command, args, context, options?)`](#runcommand)
|
|
60
|
-
- [Internal Source Layout](#internal-source-layout)
|
|
61
|
-
- [How It Works](#how-it-works)
|
|
62
|
-
- [Example](#example)
|
|
63
|
-
- [Option Schemas](#option-schemas)
|
|
64
|
-
- [Type Inference](#type-inference)
|
|
65
|
-
- [Facade of arguments](#facade-of-arguments)
|
|
66
|
-
- [Error Messages](#error-messages)
|
|
67
|
-
- [Project Boundary](#project-boundary)
|
|
68
|
-
|
|
69
|
-
## API Reference
|
|
70
|
-
|
|
71
|
-
Start at `createTerminalApp()` for regular terminal applications, then move to
|
|
72
|
-
the construction methods only when the app needs explicit command,
|
|
73
|
-
presentation, or output wiring.
|
|
74
|
-
|
|
75
|
-
### `createTerminalApp()`
|
|
76
|
-
|
|
77
|
-
`createTerminalApp()` is the top-level terminal application composition point.
|
|
78
|
-
It wires command execution, presentation rendering, and stdout/stderr delivery
|
|
79
|
-
without taking ownership of application behavior.
|
|
80
|
-
|
|
81
|
-
The checked TypeScript contract lives in [`src/terminal/app.ts`](src/terminal/app.ts).
|
|
30
|
+
Import public APIs from `icore`. Deep imports into `dist` or `src` are not part
|
|
31
|
+
of the package contract.
|
|
82
32
|
|
|
83
|
-
|
|
84
|
-
`writePreparedOutput(prepared, output)`, `runPrepared(prepared, context)`, and
|
|
85
|
-
`run(args, context)`.
|
|
33
|
+
## Contents
|
|
86
34
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
-
|
|
93
|
-
- `output` is optional; omit it to use [`createOutput()`](#createoutput);
|
|
94
|
-
- `resolveFormat` is optional and customizes how a prepared command selects a
|
|
95
|
-
presentation format.
|
|
96
|
-
|
|
97
|
-
Returned app methods:
|
|
35
|
+
- [Quick Start](#quick-start)
|
|
36
|
+
- [Choose An API Level](#choose-an-api-level)
|
|
37
|
+
- [Supported Argument Syntax](#supported-argument-syntax)
|
|
38
|
+
- [Error Handling](#error-handling)
|
|
39
|
+
- [Guides](#guides)
|
|
40
|
+
- [Project Boundary](#project-boundary)
|
|
98
41
|
|
|
99
|
-
|
|
100
|
-
[`commands.prepare(args, options?)`](#commandsprepareargs-options);
|
|
101
|
-
- `app.writePreparedOutput(prepared, output)` renders and writes already
|
|
102
|
-
obtained terminal output without running the command;
|
|
103
|
-
- `app.runPrepared(prepared, context)` runs an already prepared command, then
|
|
104
|
-
renders and writes terminal output;
|
|
105
|
-
- `app.run(args, context, options?)` prepares the command, delegates command
|
|
106
|
-
execution to [`commands.run(prepared, context)`](#commandsrunprepared-context),
|
|
107
|
-
then renders and writes terminal output.
|
|
42
|
+
## Quick Start
|
|
108
43
|
|
|
109
|
-
|
|
44
|
+
Create commands, put them in a registry, and give that registry to
|
|
45
|
+
`createTerminalApp()`:
|
|
110
46
|
|
|
111
47
|
```ts
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
});
|
|
48
|
+
import {
|
|
49
|
+
createCommand,
|
|
50
|
+
createTerminalApp
|
|
51
|
+
} from 'icore';
|
|
117
52
|
|
|
118
|
-
const args = process.argv.slice(2);
|
|
119
|
-
const exitCode = await app.run(args, context, {
|
|
120
|
-
strict: true
|
|
121
|
-
});
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
Command handlers keep ownership of application work. Terminal output methods
|
|
125
|
-
accept terminal-ready results: text, streaming text, presentation results, or no
|
|
126
|
-
output. If a command returns an application lifecycle object, run it with
|
|
127
|
-
`app.commands.run(...)`, inspect or map the result, then pass only terminal
|
|
128
|
-
output to `app.writePreparedOutput(...)`. Application DTO mapping, config
|
|
129
|
-
loading, network clients, and domain behavior stay in the consuming application.
|
|
130
|
-
|
|
131
|
-
String output is written exactly as provided. Return `\n` from the command when
|
|
132
|
-
line output is desired.
|
|
133
|
-
|
|
134
|
-
### `createCommand()`
|
|
135
|
-
|
|
136
|
-
`createCommand()` returns the command mechanics entrypoint used to build
|
|
137
|
-
`commands` for `createTerminalApp()`.
|
|
138
|
-
|
|
139
|
-
Simplified shape:
|
|
140
|
-
|
|
141
|
-
```ts
|
|
142
53
|
const command = createCommand();
|
|
143
54
|
|
|
144
|
-
command.
|
|
145
|
-
command.
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
The command definition keeps the application-specific parts: command path,
|
|
157
|
-
option schema, and handler behavior. The terminal app only needs the resulting
|
|
158
|
-
registry object.
|
|
159
|
-
|
|
160
|
-
#### `command.define(command)`
|
|
161
|
-
|
|
162
|
-
Declares one command and preserves its literal path and option schema types.
|
|
163
|
-
Use it when defining commands inline before adding them to a registry.
|
|
164
|
-
|
|
165
|
-
#### `command.registry(commands)`
|
|
166
|
-
|
|
167
|
-
Builds the `commands` object required by `createTerminalApp()`. It keeps the
|
|
168
|
-
registered command definitions, derived command names, and command flow methods
|
|
169
|
-
together.
|
|
170
|
-
|
|
171
|
-
#### `command.run(command, args, context)`
|
|
172
|
-
|
|
173
|
-
Runs a single command without a registry. Use it for focused command execution,
|
|
174
|
-
small tests, or custom flows where command path resolution is not needed.
|
|
175
|
-
|
|
176
|
-
#### `createCommands(commands)`
|
|
177
|
-
|
|
178
|
-
Use `createCommands(commands)` when the application already has command
|
|
179
|
-
definitions and does not need the `createCommand()` object form.
|
|
180
|
-
|
|
181
|
-
The returned object is the same `commands` contract consumed by
|
|
182
|
-
`createTerminalApp()`: it can resolve, prepare, and run registered commands.
|
|
183
|
-
|
|
184
|
-
Simplified shape:
|
|
185
|
-
|
|
186
|
-
```ts
|
|
187
|
-
const commands = createCommands(commandDefinitions);
|
|
188
|
-
|
|
189
|
-
commands.names;
|
|
190
|
-
commands.registry;
|
|
191
|
-
commands.resolve(positionals);
|
|
192
|
-
commands.resolveFromArgs(args);
|
|
193
|
-
commands.prepare(args, options);
|
|
194
|
-
commands.run(prepared, context);
|
|
195
|
-
commands.runFromArgs(args, context, options);
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
Use `commands.prepare(...)` and `commands.run(...)` when application code needs
|
|
199
|
-
the same two-phase flow used by `createTerminalApp()`. Use
|
|
200
|
-
`commands.runFromArgs(...)` for custom terminal boundaries that still want the
|
|
201
|
-
registry-level command flow.
|
|
202
|
-
|
|
203
|
-
#### `commands.prepare(args, options?)`
|
|
204
|
-
|
|
205
|
-
Resolves and validates a registered command without runtime context. This is
|
|
206
|
-
the same preparation step used by `app.prepare(...)` and `app.run(...)`.
|
|
207
|
-
|
|
208
|
-
#### `commands.run(prepared, context)`
|
|
209
|
-
|
|
210
|
-
Runs an already prepared command with application context. Use it after
|
|
211
|
-
`commands.prepare(...)` when the application needs a custom two-phase flow.
|
|
212
|
-
|
|
213
|
-
#### `commands.runFromArgs(args, context, options?)`
|
|
214
|
-
|
|
215
|
-
Resolves, prepares, and runs a registered command without the terminal
|
|
216
|
-
application boundary. Use it when a custom boundary owns presentation or output.
|
|
217
|
-
|
|
218
|
-
### `createPresentation()`
|
|
219
|
-
|
|
220
|
-
`createPresentation()` creates the presentation object used by
|
|
221
|
-
`createTerminalApp()` to render terminal-ready results.
|
|
222
|
-
|
|
223
|
-
Source of truth:
|
|
224
|
-
|
|
225
|
-
- [src/presentation/facade.ts](src/presentation/facade.ts) for the presentation
|
|
226
|
-
object;
|
|
227
|
-
- [src/presentation/view.ts](src/presentation/view.ts) for presentation view
|
|
228
|
-
factories;
|
|
229
|
-
- [src/presentation/result-renderer.ts](src/presentation/result-renderer.ts)
|
|
230
|
-
for rendering presentation results.
|
|
231
|
-
|
|
232
|
-
Simplified shape:
|
|
233
|
-
|
|
234
|
-
```ts
|
|
235
|
-
const presentation = createPresentation();
|
|
236
|
-
|
|
237
|
-
presentation.empty();
|
|
238
|
-
presentation.text(value);
|
|
239
|
-
presentation.record(value);
|
|
240
|
-
presentation.records(values);
|
|
241
|
-
presentation.table(rows);
|
|
242
|
-
presentation.csv(rows);
|
|
243
|
-
presentation.render(result, format);
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
The presentation object owns generic JSON, CSV, and table rendering mechanics.
|
|
247
|
-
Application code still maps domain objects to presentation-ready values.
|
|
248
|
-
|
|
249
|
-
#### `presentation.empty()`
|
|
250
|
-
|
|
251
|
-
Creates a presentation result with no terminal output.
|
|
252
|
-
|
|
253
|
-
#### `presentation.text(value)`
|
|
254
|
-
|
|
255
|
-
Wraps ready terminal text. Use it when the command already owns the final text.
|
|
256
|
-
|
|
257
|
-
#### `presentation.record(value)`
|
|
258
|
-
|
|
259
|
-
Creates a generic one-record view. The renderer can print it as table, CSV, or
|
|
260
|
-
JSON depending on the selected format.
|
|
261
|
-
|
|
262
|
-
#### `presentation.records(values)`
|
|
263
|
-
|
|
264
|
-
Creates a generic multi-record view. Application code still decides which fields
|
|
265
|
-
belong in each record.
|
|
266
|
-
|
|
267
|
-
#### `presentation.table(rows)`
|
|
268
|
-
|
|
269
|
-
Creates an explicit table view from prepared text rows.
|
|
270
|
-
|
|
271
|
-
#### `presentation.csv(rows)`
|
|
272
|
-
|
|
273
|
-
Creates an explicit CSV view from scalar rows.
|
|
274
|
-
|
|
275
|
-
#### `presentation.render(result, format?)`
|
|
276
|
-
|
|
277
|
-
Renders a presentation result to terminal text. The default format is `table`.
|
|
278
|
-
|
|
279
|
-
#### `presentation.renderers.*`
|
|
280
|
-
|
|
281
|
-
Exposes lower-level JSON, table, and CSV renderers for custom presentation
|
|
282
|
-
composition. Prefer `presentation.render(...)` for regular terminal commands.
|
|
283
|
-
|
|
284
|
-
### `createOutput()`
|
|
285
|
-
|
|
286
|
-
`createOutput()` creates the output object used by `createTerminalApp()` to
|
|
287
|
-
write rendered text.
|
|
288
|
-
|
|
289
|
-
Source of truth:
|
|
290
|
-
|
|
291
|
-
- [src/output/facade.ts](src/output/facade.ts) for the output object;
|
|
292
|
-
- [src/output/node-writer.ts](src/output/node-writer.ts) for stdout and stderr
|
|
293
|
-
writers;
|
|
294
|
-
- [src/output/text-writer.ts](src/output/text-writer.ts) for backpressure-aware
|
|
295
|
-
text writing.
|
|
296
|
-
|
|
297
|
-
Simplified shape:
|
|
298
|
-
|
|
299
|
-
```ts
|
|
300
|
-
const output = createOutput();
|
|
301
|
-
|
|
302
|
-
await output.write(chunk);
|
|
303
|
-
await output.error(chunk);
|
|
304
|
-
await output.stdout.write(chunk);
|
|
305
|
-
await output.stderr.write(chunk);
|
|
306
|
-
```
|
|
307
|
-
|
|
308
|
-
The default output writes regular text to `stdout` and diagnostic text to
|
|
309
|
-
`stderr`. Pass custom sinks when tests or applications need controlled output.
|
|
310
|
-
|
|
311
|
-
#### `output.write(chunk)`
|
|
312
|
-
|
|
313
|
-
Writes regular terminal output through stdout.
|
|
314
|
-
|
|
315
|
-
#### `output.error(chunk)`
|
|
316
|
-
|
|
317
|
-
Writes diagnostic terminal output through stderr.
|
|
318
|
-
|
|
319
|
-
#### `output.stdout.write(chunk)`
|
|
320
|
-
|
|
321
|
-
Writes directly to the stdout channel. Use this only when a specific channel
|
|
322
|
-
must be passed around.
|
|
323
|
-
|
|
324
|
-
#### `output.stderr.write(chunk)`
|
|
325
|
-
|
|
326
|
-
Writes directly to the stderr channel. Use this only when a specific channel
|
|
327
|
-
must be passed around.
|
|
328
|
-
|
|
329
|
-
### Lower-Level Mechanics
|
|
330
|
-
|
|
331
|
-
Lower-level mechanics sit behind the construction methods. They are kept out of
|
|
332
|
-
the main navigation to keep the application construction path first.
|
|
333
|
-
|
|
334
|
-
For application code, prefer `createTerminalApp()` first. Drop to this level
|
|
335
|
-
only when the terminal application flow or construction methods are too coarse.
|
|
336
|
-
|
|
337
|
-
Source of truth:
|
|
338
|
-
|
|
339
|
-
- [src/argv/parser.ts](src/argv/parser.ts) for raw argv parsing;
|
|
340
|
-
- [src/options/parser.ts](src/options/parser.ts) for option value validation;
|
|
341
|
-
- [src/command/mechanics.ts](src/command/mechanics.ts) for command resolution,
|
|
342
|
-
preparation, and execution.
|
|
343
|
-
|
|
344
|
-
#### Parsing And Resolution
|
|
345
|
-
|
|
346
|
-
This group turns raw arguments or positionals into a selected command.
|
|
347
|
-
|
|
348
|
-
- <a id="parseargv"></a>`parseArgv(args, schema?)` parses raw CLI arguments into positionals and raw
|
|
349
|
-
option values;
|
|
350
|
-
- <a id="resolvecommand"></a>`resolveCommand(registry, positionals)` resolves from already parsed
|
|
351
|
-
positionals;
|
|
352
|
-
- <a id="resolvecommandfromargs"></a>`resolveCommandFromArgs(registry, args)` resolves from raw CLI arguments by
|
|
353
|
-
using registered command schemas.
|
|
354
|
-
|
|
355
|
-
Use these methods when command selection is needed without validation or
|
|
356
|
-
execution.
|
|
357
|
-
|
|
358
|
-
#### Preparation
|
|
359
|
-
|
|
360
|
-
This group validates command input without runtime context.
|
|
361
|
-
|
|
362
|
-
- <a id="parseoptionsdetailed"></a>`parseOptionsDetailed(schema, values)` validates raw option values and keeps
|
|
363
|
-
option presence metadata;
|
|
364
|
-
- <a id="preparecommandfromargs"></a>`prepareCommandFromArgs(registry, args, options?)` resolves and validates a
|
|
365
|
-
registered command;
|
|
366
|
-
- `options.strict` rejects options before the command path.
|
|
367
|
-
|
|
368
|
-
Use preparation when runtime resources should be created only after command
|
|
369
|
-
selection is known.
|
|
370
|
-
|
|
371
|
-
#### Execution
|
|
372
|
-
|
|
373
|
-
This group calls command handlers after command input is prepared.
|
|
374
|
-
|
|
375
|
-
- <a id="runpreparedcommand"></a>`runPreparedCommand(prepared, context)` runs an already prepared command;
|
|
376
|
-
- <a id="runcommandfromregistry"></a>`runCommandFromRegistry(registry, args, context, options?)` prepares and runs
|
|
377
|
-
a command from a registry;
|
|
378
|
-
- <a id="runcommand"></a>`runCommand(command, args, context, options?)` runs a single command without a
|
|
379
|
-
registry.
|
|
380
|
-
|
|
381
|
-
Use execution primitives for custom boundaries. Regular CLI applications should
|
|
382
|
-
usually stay at `createTerminalApp()`.
|
|
383
|
-
|
|
384
|
-
## Internal Source Layout
|
|
385
|
-
|
|
386
|
-
The public package entrypoint remains `src/index.ts`. Internal source files are
|
|
387
|
-
grouped by CLI framework responsibility:
|
|
388
|
-
|
|
389
|
-
```text
|
|
390
|
-
src/
|
|
391
|
-
argv/ raw argv token parsing
|
|
392
|
-
options/ option schemas and option value validation
|
|
393
|
-
command/ command resolution and execution mechanics
|
|
394
|
-
presentation/ terminal view models and JSON/CSV/table renderers
|
|
395
|
-
output/ stdout/stderr writer boundaries
|
|
396
|
-
terminal/ command + presentation + output app composition
|
|
397
|
-
errors/ machine-readable icore errors
|
|
398
|
-
```
|
|
399
|
-
|
|
400
|
-
Consumers should continue importing from `icore`; deep imports are an internal
|
|
401
|
-
source layout detail.
|
|
402
|
-
|
|
403
|
-
## How It Works
|
|
404
|
-
|
|
405
|
-

|
|
406
|
-
|
|
407
|
-
## Example
|
|
408
|
-
|
|
409
|
-
More examples live in [examples/readme.md](examples/readme.md), including
|
|
410
|
-
option schemas, CLI argument syntax, practical CLI patterns, and lower-level
|
|
411
|
-
primitives.
|
|
412
|
-
|
|
413
|
-
```ts
|
|
414
|
-
import { defineCommand, runCommand } from 'icore';
|
|
415
|
-
|
|
416
|
-
const exampleCommand = defineCommand({
|
|
417
|
-
path: ['hello'],
|
|
418
|
-
options: {
|
|
419
|
-
name: {
|
|
420
|
-
type: 'string',
|
|
421
|
-
default: 'world'
|
|
55
|
+
const commands = command.registry([
|
|
56
|
+
command.define({
|
|
57
|
+
path: ['hello'],
|
|
58
|
+
options: {
|
|
59
|
+
name: {
|
|
60
|
+
type: 'string',
|
|
61
|
+
default: 'world'
|
|
62
|
+
},
|
|
63
|
+
uppercase: {
|
|
64
|
+
type: 'boolean'
|
|
65
|
+
}
|
|
422
66
|
},
|
|
423
|
-
|
|
424
|
-
|
|
67
|
+
handle({ options }) {
|
|
68
|
+
const greeting = `Hello, ${options.name}!`;
|
|
69
|
+
|
|
70
|
+
return `${options.uppercase ? greeting.toUpperCase() : greeting}\n`;
|
|
425
71
|
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
const message = `Hello, ${options.name}!`;
|
|
72
|
+
})
|
|
73
|
+
] as const);
|
|
429
74
|
|
|
430
|
-
|
|
431
|
-
}
|
|
432
|
-
});
|
|
75
|
+
const app = createTerminalApp({ commands });
|
|
433
76
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
77
|
+
async function main(args: readonly string[]): Promise<void> {
|
|
78
|
+
process.exitCode = await app.run(args, undefined, {
|
|
79
|
+
strict: true
|
|
80
|
+
});
|
|
81
|
+
}
|
|
439
82
|
|
|
440
|
-
|
|
83
|
+
void main(process.argv.slice(2));
|
|
441
84
|
```
|
|
442
85
|
|
|
443
|
-
|
|
86
|
+
After compiling the application:
|
|
444
87
|
|
|
445
88
|
```console
|
|
446
|
-
$ node cli.js hello
|
|
447
|
-
|
|
448
|
-
```
|
|
449
|
-
|
|
450
|
-
The command handler receives parsed options, user-provided option metadata,
|
|
451
|
-
remaining positionals, prepared payload, and caller provided context.
|
|
452
|
-
|
|
453
|
-
## Option Schemas
|
|
454
|
-
|
|
455
|
-
Options are described as plain objects.
|
|
456
|
-
|
|
457
|
-
**Option names are exact.** `icore` does not normalize `camelCase` to
|
|
458
|
-
`kebab-case`. Use quoted object keys when your public CLI option contains
|
|
459
|
-
`-`.
|
|
460
|
-
|
|
461
|
-
Each option can define an optional short alias:
|
|
462
|
-
|
|
463
|
-
```ts
|
|
464
|
-
const schema = {
|
|
465
|
-
name: {
|
|
466
|
-
type: 'string',
|
|
467
|
-
alias: 'n'
|
|
468
|
-
},
|
|
469
|
-
uppercase: {
|
|
470
|
-
type: 'boolean',
|
|
471
|
-
alias: 'u'
|
|
472
|
-
}
|
|
473
|
-
} as const;
|
|
474
|
-
```
|
|
475
|
-
|
|
476
|
-
Aliases must be a single ASCII letter and unique within the schema. Parsed
|
|
477
|
-
values are always returned by long option name.
|
|
478
|
-
|
|
479
|
-
### `type: 'string'`
|
|
480
|
-
|
|
481
|
-
```ts
|
|
482
|
-
const schema = {
|
|
483
|
-
name: {
|
|
484
|
-
type: 'string',
|
|
485
|
-
required: true
|
|
486
|
-
},
|
|
487
|
-
style: {
|
|
488
|
-
type: 'string',
|
|
489
|
-
choices: ['short', 'long'],
|
|
490
|
-
default: 'short'
|
|
491
|
-
}
|
|
492
|
-
} as const;
|
|
493
|
-
```
|
|
494
|
-
|
|
495
|
-
String options reject missing required values, blank strings such as `--name=`,
|
|
496
|
-
boolean flag form, and values outside `choices`.
|
|
497
|
-
|
|
498
|
-
### `type: 'boolean'`
|
|
499
|
-
|
|
500
|
-
```ts
|
|
501
|
-
const schema = {
|
|
502
|
-
uppercase: {
|
|
503
|
-
type: 'boolean'
|
|
504
|
-
}
|
|
505
|
-
} as const;
|
|
506
|
-
```
|
|
89
|
+
$ node dist/cli.js hello
|
|
90
|
+
Hello, world!
|
|
507
91
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
```sh
|
|
511
|
-
--uppercase
|
|
512
|
-
--no-uppercase
|
|
513
|
-
```
|
|
514
|
-
|
|
515
|
-
Explicit values are rejected:
|
|
516
|
-
|
|
517
|
-
```sh
|
|
518
|
-
--uppercase=true
|
|
519
|
-
--uppercase=false
|
|
520
|
-
--uppercase=yes
|
|
521
|
-
--uppercase=
|
|
522
|
-
```
|
|
523
|
-
|
|
524
|
-
`--uppercase false` keeps `--uppercase` as `true` and leaves `false` as a positional
|
|
525
|
-
argument.
|
|
526
|
-
|
|
527
|
-
Use `syntax: 'flag'` when a boolean option should accept only flag form:
|
|
528
|
-
|
|
529
|
-
```ts
|
|
530
|
-
const schema = {
|
|
531
|
-
uppercase: {
|
|
532
|
-
type: 'boolean',
|
|
533
|
-
default: false,
|
|
534
|
-
syntax: 'flag'
|
|
535
|
-
}
|
|
536
|
-
} as const;
|
|
92
|
+
$ node dist/cli.js hello --name Alice --uppercase
|
|
93
|
+
HELLO, ALICE!
|
|
537
94
|
```
|
|
538
95
|
|
|
539
|
-
|
|
540
|
-
|
|
96
|
+
The schema determines the handler's option types. Required options and options
|
|
97
|
+
with defaults are always present; optional options are returned as
|
|
98
|
+
`T | undefined`.
|
|
541
99
|
|
|
542
|
-
|
|
100
|
+
`strict: true` rejects options placed before the command path. String output is
|
|
101
|
+
written exactly as returned, so include `\n` when line output is intended.
|
|
543
102
|
|
|
544
|
-
|
|
545
|
-
const schema = {
|
|
546
|
-
limit: {
|
|
547
|
-
type: 'number',
|
|
548
|
-
integer: true,
|
|
549
|
-
min: 1,
|
|
550
|
-
max: 1000,
|
|
551
|
-
default: 100
|
|
552
|
-
}
|
|
553
|
-
} as const;
|
|
554
|
-
```
|
|
103
|
+
## Choose An API Level
|
|
555
104
|
|
|
556
|
-
|
|
557
|
-
constraints. Defaults are validated with the same rules as user-provided values.
|
|
105
|
+
Start with the highest-level API that fits the application:
|
|
558
106
|
|
|
559
|
-
|
|
107
|
+
| Need | Start with | Detailed guide |
|
|
108
|
+
| --- | --- | --- |
|
|
109
|
+
| Complete terminal application | `createTerminalApp()` | [Terminal App](examples/terminal-app.md) |
|
|
110
|
+
| Commands and option schemas | `createCommand()` or `createCommands()` | [Option Schemas](examples/option-schemas.md) |
|
|
111
|
+
| Custom prepare/execute lifecycle | `commands.prepare()` and `commands.run()` | [Custom Command Flow](examples/custom-command-flow.md) |
|
|
112
|
+
| Presentation without command execution | `createPresentation()` | [Presentation And Output](examples/presentation-output.md) |
|
|
113
|
+
| Explicit stdout/stderr writing | `createOutput()` | [Output Writers](examples/output-writers.md) |
|
|
114
|
+
| Parser and resolver primitives | `parseArgv()`, `resolveCommand()`, and related exports | [Primitive Mechanics](examples/readme.md#primitive-mechanics) |
|
|
560
115
|
|
|
561
|
-
|
|
116
|
+
The terminal application exposes the main lifecycle operations:
|
|
562
117
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
default: 'world'
|
|
570
|
-
},
|
|
571
|
-
uppercase: {
|
|
572
|
-
type: 'boolean'
|
|
573
|
-
}
|
|
574
|
-
} as const;
|
|
118
|
+
- `app.run(args, context, options?)` prepares, executes, renders, and writes;
|
|
119
|
+
- `app.prepare(args, options?)` validates input without runtime context;
|
|
120
|
+
- `app.runPrepared(prepared, context)` executes an already prepared command;
|
|
121
|
+
- `app.writePreparedOutput(prepared, output)` writes caller-obtained output;
|
|
122
|
+
- `app.reportError(error, context?)` applies the configured terminal error
|
|
123
|
+
policy and returns a process-style exit code.
|
|
575
124
|
|
|
576
|
-
|
|
577
|
-
|
|
125
|
+
Command handlers receive parsed `options`, option-presence metadata in
|
|
126
|
+
`provided`, remaining `positionals`, caller-owned `context`, and an optional
|
|
127
|
+
prepared `payload`.
|
|
578
128
|
|
|
579
|
-
|
|
129
|
+
Supported terminal results are strings, async string streams, presentation
|
|
130
|
+
results, and `undefined`:
|
|
580
131
|
|
|
581
|
-
```
|
|
582
|
-
|
|
583
|
-
name: string;
|
|
584
|
-
uppercase: boolean | undefined;
|
|
585
|
-
};
|
|
132
|
+
```text
|
|
133
|
+
argv → resolve → validate/prepare → execute → render → stdout/stderr
|
|
586
134
|
```
|
|
587
135
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
Use `InferProvidedOptions` when you need the option presence type explicitly.
|
|
592
|
-
|
|
593
|
-
```ts
|
|
594
|
-
import type { InferProvidedOptions } from 'icore';
|
|
136
|
+
Exact public exports live in [`src/index.ts`](src/index.ts) and in the bundled
|
|
137
|
+
TypeScript declarations.
|
|
595
138
|
|
|
596
|
-
|
|
597
|
-
```
|
|
139
|
+
## Supported Argument Syntax
|
|
598
140
|
|
|
599
|
-
|
|
600
|
-
specified that option explicitly; defaults keep the flag `false`.
|
|
141
|
+
The supported syntax is intentionally small and predictable:
|
|
601
142
|
|
|
602
|
-
|
|
143
|
+
| Form | Example | Notes |
|
|
144
|
+
| --- | --- | --- |
|
|
145
|
+
| Long option with separate value | `--name Alice` | String and number options consume a value |
|
|
146
|
+
| Long option with attached value | `--name=Alice` | Equivalent to the separate form |
|
|
147
|
+
| Boolean flag | `--verbose` | Produces `true` |
|
|
148
|
+
| Boolean negation | `--no-cache` | Supported for known boolean options unless `syntax: 'flag'` |
|
|
149
|
+
| Short alias | `-v`, `-n Alice` | Must be declared in the schema as one ASCII letter |
|
|
150
|
+
| Option terminator | `--` | Every following token becomes positional |
|
|
603
151
|
|
|
604
|
-
|
|
605
|
-
|
|
152
|
+
Option names are exact: `icore` does not convert `camelCase` to `kebab-case`.
|
|
153
|
+
Explicit boolean values such as `--verbose=true`, attached short values such as
|
|
154
|
+
`-nAlice`, and grouped aliases such as `-abc` are not supported.
|
|
606
155
|
|
|
607
|
-
|
|
608
|
-
|
|
156
|
+
See [CLI Argument Syntax](examples/cli-argument-syntax.md) for parsing examples,
|
|
157
|
+
edge cases, duplicate handling, and the option terminator contract.
|
|
609
158
|
|
|
610
|
-
|
|
159
|
+
## Error Handling
|
|
611
160
|
|
|
612
|
-
|
|
613
|
-
|
|
161
|
+
CLI parsing, validation, resolution, and definition failures are reported as
|
|
162
|
+
`IcoreError`. It extends `Error` with:
|
|
614
163
|
|
|
615
|
-
|
|
616
|
-
|
|
164
|
+
- a stable machine-readable `code`;
|
|
165
|
+
- a `usage` or `definition` category;
|
|
166
|
+
- required, code-specific `details`.
|
|
617
167
|
|
|
618
|
-
`
|
|
168
|
+
Use `isIcoreError(...)` instead of manually casting details. Passing a code to
|
|
169
|
+
the guard narrows the corresponding details shape:
|
|
619
170
|
|
|
620
171
|
```ts
|
|
621
|
-
|
|
622
|
-
|
|
172
|
+
import {
|
|
173
|
+
createTerminalApp,
|
|
174
|
+
isIcoreError
|
|
175
|
+
} from 'icore';
|
|
623
176
|
|
|
624
|
-
|
|
177
|
+
const help = 'Usage: cli hello [--name value]';
|
|
625
178
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
Attached short values such as `-nvalue` and grouped short booleans such as
|
|
635
|
-
`-abc` are not supported yet. Unknown short tokens remain positional for
|
|
636
|
-
compatibility.
|
|
637
|
-
|
|
638
|
-
Negated syntax such as `--no-cache` is interpreted as `cache: false` when
|
|
639
|
-
`cache` is a known boolean option without `syntax: 'flag'`. Unknown negated
|
|
640
|
-
options, negation for string or number options, and negation for flag-only
|
|
641
|
-
boolean options are rejected.
|
|
642
|
-
|
|
643
|
-
## Error Messages
|
|
644
|
-
|
|
645
|
-
`icore` throws `IcoreError` objects for CLI parsing, option validation, and
|
|
646
|
-
command resolution failures. `IcoreError` extends the regular `Error` class and
|
|
647
|
-
adds a stable machine-readable `code` plus structured `details`.
|
|
179
|
+
const app = createTerminalApp({
|
|
180
|
+
commands,
|
|
181
|
+
errorPolicy: {
|
|
182
|
+
renderError(error) {
|
|
183
|
+
const message = error instanceof Error
|
|
184
|
+
? error.message
|
|
185
|
+
: String(error);
|
|
648
186
|
|
|
649
|
-
|
|
650
|
-
|
|
187
|
+
if (isIcoreError(error, 'UNKNOWN_COMMAND')) {
|
|
188
|
+
return `${message}\n\n${help}\n`;
|
|
189
|
+
}
|
|
651
190
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
printHelp();
|
|
660
|
-
process.exitCode = 2;
|
|
661
|
-
return;
|
|
191
|
+
return `${message}\n`;
|
|
192
|
+
},
|
|
193
|
+
resolveExitCode(error) {
|
|
194
|
+
return isIcoreError(error) && error.category === 'usage'
|
|
195
|
+
? 2
|
|
196
|
+
: 1;
|
|
197
|
+
}
|
|
662
198
|
}
|
|
663
|
-
|
|
664
|
-
throw error;
|
|
665
|
-
}
|
|
199
|
+
});
|
|
666
200
|
```
|
|
667
201
|
|
|
668
|
-
|
|
202
|
+
Inside the `UNKNOWN_COMMAND` branch, fields such as `error.details.command` and
|
|
203
|
+
`error.details.positionals` are strongly typed. `IcoreErrorDetailsMap` is the
|
|
204
|
+
public source of truth for every code. Direct `new IcoreError(...)` calls must
|
|
205
|
+
provide a third argument matching the selected code.
|
|
669
206
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
| 'UNEXPECTED_ARGUMENT'
|
|
674
|
-
| 'DUPLICATE_ARGUMENT'
|
|
675
|
-
| 'EXPECTED_REQUIRED_ARGUMENT'
|
|
676
|
-
| 'INVALID_OPTION_TYPE'
|
|
677
|
-
| 'INVALID_OPTION_CHOICE'
|
|
678
|
-
| 'UNEXPECTED_POSITIONAL'
|
|
679
|
-
| 'INVALID_OPTION_ALIAS'
|
|
680
|
-
| 'DUPLICATE_ALIAS'
|
|
681
|
-
| 'INVALID_OPTION_DEFAULT'
|
|
682
|
-
| 'DUPLICATE_COMMAND';
|
|
683
|
-
```
|
|
207
|
+
Without a custom policy, terminal apps write `Error.message + "\n"` (or
|
|
208
|
+
`String(error) + "\n"` for other thrown values) and return exit code `1`.
|
|
209
|
+
Application-specific help remains application policy.
|
|
684
210
|
|
|
685
|
-
|
|
686
|
-
|
|
211
|
+
Custom lifecycles can call `app.reportError(...)` to reuse the same rendering
|
|
212
|
+
and exit-code policy. The complete prepare, execute, write, and external phase
|
|
213
|
+
flow is shown in the
|
|
214
|
+
[Terminal App guide](examples/terminal-app.md#reuse-error-reporting-in-a-custom-lifecycle).
|
|
687
215
|
|
|
688
|
-
|
|
689
|
-
$ node cli.js hello --unknown
|
|
690
|
-
Unexpected argument '--unknown'
|
|
216
|
+
## Guides
|
|
691
217
|
|
|
692
|
-
|
|
693
|
-
|
|
218
|
+
The [examples index](examples/readme.md) routes from regular terminal apps to
|
|
219
|
+
lower-level mechanics. Useful starting points:
|
|
694
220
|
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
221
|
+
- [Terminal App](examples/terminal-app.md) — regular application composition,
|
|
222
|
+
format resolution, and reusable error reporting;
|
|
223
|
+
- [Option Schemas](examples/option-schemas.md) — strings, booleans, numbers,
|
|
224
|
+
choices, defaults, aliases, and inferred types;
|
|
225
|
+
- [Practical CLI Patterns](examples/practical-cli-patterns.md) — help/version
|
|
226
|
+
shortcuts, shared options, and compatibility aliases;
|
|
227
|
+
- [Command Resolution](examples/command-resolution.md) — registries, explicit
|
|
228
|
+
resolution, and command-name guards;
|
|
229
|
+
- [Two-Phase Primitives](examples/two-phase-primitives.md) — preparation,
|
|
230
|
+
payloads, execution, and provided-option metadata;
|
|
231
|
+
- [Presentation Primitives](examples/presentation-primitives.md) — text, record,
|
|
232
|
+
table, CSV, JSON, and direct renderers.
|
|
698
233
|
|
|
699
|
-
|
|
700
|
-
|
|
234
|
+
Release history is recorded in [CHANGELOG.md](CHANGELOG.md). Directional
|
|
235
|
+
decisions live in [docs/roadmap.md](docs/roadmap.md).
|
|
701
236
|
|
|
702
237
|
## Project Boundary
|
|
703
238
|
|
|
704
|
-
`icore` is
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
Good responsibilities for `icore`:
|
|
239
|
+
`icore` is a small terminal mechanics module. It owns generic behavior such as
|
|
240
|
+
option validation, command resolution, typed handler input, presentation
|
|
241
|
+
rendering, error contracts, and stdout/stderr delivery.
|
|
708
242
|
|
|
709
|
-
|
|
710
|
-
-
|
|
711
|
-
|
|
712
|
-
- typed command handler input;
|
|
713
|
-
- generic JSON, CSV, and text table rendering;
|
|
714
|
-
- stdout/stderr text writer mechanics.
|
|
243
|
+
It does not own application DTO mapping, API calls, configuration loading,
|
|
244
|
+
domain-specific validation, lifecycle resources, or help content. Keep those
|
|
245
|
+
responsibilities in the consuming application.
|
|
715
246
|
|
|
716
|
-
|
|
717
|
-
loading, and domain behavior stay outside `icore`.
|
|
247
|
+
Licensed under the [MIT License](LICENSE).
|