icore 1.0.18 → 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 +93 -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 +42 -4
- package/dist/terminal/app.js +78 -25
- package/examples/terminal-app.md +67 -0
- package/examples/two-phase-primitives.md +37 -0
- package/package.json +2 -2
- package/readme.md +170 -632
package/readme.md
CHANGED
|
@@ -5,705 +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.
|
|
30
|
+
Import public APIs from `icore`. Deep imports into `dist` or `src` are not part
|
|
31
|
+
of the package contract.
|
|
80
32
|
|
|
81
|
-
|
|
33
|
+
## Contents
|
|
82
34
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
-
|
|
89
|
-
[`command.registry(commands)`](#commandregistrycommands), or directly with
|
|
90
|
-
[`createCommands(commands)`](#createcommandscommands);
|
|
91
|
-
- `presentation` is optional; omit it to use [`createPresentation()`](#createpresentation);
|
|
92
|
-
- `output` is optional; omit it to use [`createOutput()`](#createoutput);
|
|
93
|
-
- `resolveFormat` is optional and customizes how a prepared command selects a
|
|
94
|
-
presentation format.
|
|
95
|
-
|
|
96
|
-
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)
|
|
97
41
|
|
|
98
|
-
|
|
99
|
-
[`commands.prepare(args, options?)`](#commandsprepareargs-options);
|
|
100
|
-
- `app.runPrepared(prepared, context)` runs an already prepared command, then
|
|
101
|
-
renders and writes terminal output;
|
|
102
|
-
- `app.run(args, context, options?)` prepares the command, delegates command
|
|
103
|
-
execution to [`commands.run(prepared, context)`](#commandsrunprepared-context),
|
|
104
|
-
then renders and writes terminal output.
|
|
42
|
+
## Quick Start
|
|
105
43
|
|
|
106
|
-
|
|
44
|
+
Create commands, put them in a registry, and give that registry to
|
|
45
|
+
`createTerminalApp()`:
|
|
107
46
|
|
|
108
47
|
```ts
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
const args = process.argv.slice(2);
|
|
116
|
-
const exitCode = await app.run(args, context, {
|
|
117
|
-
strict: true
|
|
118
|
-
});
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
Command handlers keep ownership of application work. The terminal app only
|
|
122
|
-
accepts terminal-ready results: text, streaming text, presentation results, or
|
|
123
|
-
no output. Application DTO mapping, config loading, network clients, and domain
|
|
124
|
-
behavior stay in the consuming application.
|
|
125
|
-
|
|
126
|
-
### `createCommand()`
|
|
48
|
+
import {
|
|
49
|
+
createCommand,
|
|
50
|
+
createTerminalApp
|
|
51
|
+
} from 'icore';
|
|
127
52
|
|
|
128
|
-
`createCommand()` returns the command mechanics entrypoint used to build
|
|
129
|
-
`commands` for `createTerminalApp()`.
|
|
130
|
-
|
|
131
|
-
Simplified shape:
|
|
132
|
-
|
|
133
|
-
```ts
|
|
134
53
|
const command = createCommand();
|
|
135
54
|
|
|
136
|
-
command.
|
|
137
|
-
command.
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
The command definition keeps the application-specific parts: command path,
|
|
149
|
-
option schema, and handler behavior. The terminal app only needs the resulting
|
|
150
|
-
registry object.
|
|
151
|
-
|
|
152
|
-
#### `command.define(command)`
|
|
153
|
-
|
|
154
|
-
Declares one command and preserves its literal path and option schema types.
|
|
155
|
-
Use it when defining commands inline before adding them to a registry.
|
|
156
|
-
|
|
157
|
-
#### `command.registry(commands)`
|
|
158
|
-
|
|
159
|
-
Builds the `commands` object required by `createTerminalApp()`. It keeps the
|
|
160
|
-
registered command definitions, derived command names, and command flow methods
|
|
161
|
-
together.
|
|
162
|
-
|
|
163
|
-
#### `command.run(command, args, context)`
|
|
164
|
-
|
|
165
|
-
Runs a single command without a registry. Use it for focused command execution,
|
|
166
|
-
small tests, or custom flows where command path resolution is not needed.
|
|
167
|
-
|
|
168
|
-
#### `createCommands(commands)`
|
|
169
|
-
|
|
170
|
-
Use `createCommands(commands)` when the application already has command
|
|
171
|
-
definitions and does not need the `createCommand()` object form.
|
|
172
|
-
|
|
173
|
-
The returned object is the same `commands` contract consumed by
|
|
174
|
-
`createTerminalApp()`: it can resolve, prepare, and run registered commands.
|
|
175
|
-
|
|
176
|
-
Simplified shape:
|
|
177
|
-
|
|
178
|
-
```ts
|
|
179
|
-
const commands = createCommands(commandDefinitions);
|
|
180
|
-
|
|
181
|
-
commands.names;
|
|
182
|
-
commands.registry;
|
|
183
|
-
commands.resolve(positionals);
|
|
184
|
-
commands.resolveFromArgs(args);
|
|
185
|
-
commands.prepare(args, options);
|
|
186
|
-
commands.run(prepared, context);
|
|
187
|
-
commands.runFromArgs(args, context, options);
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
Use `commands.prepare(...)` and `commands.run(...)` when application code needs
|
|
191
|
-
the same two-phase flow used by `createTerminalApp()`. Use
|
|
192
|
-
`commands.runFromArgs(...)` for custom terminal boundaries that still want the
|
|
193
|
-
registry-level command flow.
|
|
194
|
-
|
|
195
|
-
#### `commands.prepare(args, options?)`
|
|
196
|
-
|
|
197
|
-
Resolves and validates a registered command without runtime context. This is
|
|
198
|
-
the same preparation step used by `app.prepare(...)` and `app.run(...)`.
|
|
199
|
-
|
|
200
|
-
#### `commands.run(prepared, context)`
|
|
201
|
-
|
|
202
|
-
Runs an already prepared command with application context. Use it after
|
|
203
|
-
`commands.prepare(...)` when the application needs a custom two-phase flow.
|
|
204
|
-
|
|
205
|
-
#### `commands.runFromArgs(args, context, options?)`
|
|
206
|
-
|
|
207
|
-
Resolves, prepares, and runs a registered command without the terminal
|
|
208
|
-
application boundary. Use it when a custom boundary owns presentation or output.
|
|
209
|
-
|
|
210
|
-
### `createPresentation()`
|
|
211
|
-
|
|
212
|
-
`createPresentation()` creates the presentation object used by
|
|
213
|
-
`createTerminalApp()` to render terminal-ready results.
|
|
214
|
-
|
|
215
|
-
Source of truth:
|
|
216
|
-
|
|
217
|
-
- [src/presentation/facade.ts](src/presentation/facade.ts) for the presentation
|
|
218
|
-
object;
|
|
219
|
-
- [src/presentation/view.ts](src/presentation/view.ts) for presentation view
|
|
220
|
-
factories;
|
|
221
|
-
- [src/presentation/result-renderer.ts](src/presentation/result-renderer.ts)
|
|
222
|
-
for rendering presentation results.
|
|
223
|
-
|
|
224
|
-
Simplified shape:
|
|
225
|
-
|
|
226
|
-
```ts
|
|
227
|
-
const presentation = createPresentation();
|
|
228
|
-
|
|
229
|
-
presentation.empty();
|
|
230
|
-
presentation.text(value);
|
|
231
|
-
presentation.record(value);
|
|
232
|
-
presentation.records(values);
|
|
233
|
-
presentation.table(rows);
|
|
234
|
-
presentation.csv(rows);
|
|
235
|
-
presentation.render(result, format);
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
The presentation object owns generic JSON, CSV, and table rendering mechanics.
|
|
239
|
-
Application code still maps domain objects to presentation-ready values.
|
|
240
|
-
|
|
241
|
-
#### `presentation.empty()`
|
|
242
|
-
|
|
243
|
-
Creates a presentation result with no terminal output.
|
|
244
|
-
|
|
245
|
-
#### `presentation.text(value)`
|
|
246
|
-
|
|
247
|
-
Wraps ready terminal text. Use it when the command already owns the final text.
|
|
248
|
-
|
|
249
|
-
#### `presentation.record(value)`
|
|
250
|
-
|
|
251
|
-
Creates a generic one-record view. The renderer can print it as table, CSV, or
|
|
252
|
-
JSON depending on the selected format.
|
|
253
|
-
|
|
254
|
-
#### `presentation.records(values)`
|
|
255
|
-
|
|
256
|
-
Creates a generic multi-record view. Application code still decides which fields
|
|
257
|
-
belong in each record.
|
|
258
|
-
|
|
259
|
-
#### `presentation.table(rows)`
|
|
260
|
-
|
|
261
|
-
Creates an explicit table view from prepared text rows.
|
|
262
|
-
|
|
263
|
-
#### `presentation.csv(rows)`
|
|
264
|
-
|
|
265
|
-
Creates an explicit CSV view from scalar rows.
|
|
266
|
-
|
|
267
|
-
#### `presentation.render(result, format?)`
|
|
268
|
-
|
|
269
|
-
Renders a presentation result to terminal text. The default format is `table`.
|
|
270
|
-
|
|
271
|
-
#### `presentation.renderers.*`
|
|
272
|
-
|
|
273
|
-
Exposes lower-level JSON, table, and CSV renderers for custom presentation
|
|
274
|
-
composition. Prefer `presentation.render(...)` for regular terminal commands.
|
|
275
|
-
|
|
276
|
-
### `createOutput()`
|
|
277
|
-
|
|
278
|
-
`createOutput()` creates the output object used by `createTerminalApp()` to
|
|
279
|
-
write rendered text.
|
|
280
|
-
|
|
281
|
-
Source of truth:
|
|
282
|
-
|
|
283
|
-
- [src/output/facade.ts](src/output/facade.ts) for the output object;
|
|
284
|
-
- [src/output/node-writer.ts](src/output/node-writer.ts) for stdout and stderr
|
|
285
|
-
writers;
|
|
286
|
-
- [src/output/text-writer.ts](src/output/text-writer.ts) for backpressure-aware
|
|
287
|
-
text writing.
|
|
288
|
-
|
|
289
|
-
Simplified shape:
|
|
290
|
-
|
|
291
|
-
```ts
|
|
292
|
-
const output = createOutput();
|
|
293
|
-
|
|
294
|
-
await output.write(chunk);
|
|
295
|
-
await output.error(chunk);
|
|
296
|
-
await output.stdout.write(chunk);
|
|
297
|
-
await output.stderr.write(chunk);
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
The default output writes regular text to `stdout` and diagnostic text to
|
|
301
|
-
`stderr`. Pass custom sinks when tests or applications need controlled output.
|
|
302
|
-
|
|
303
|
-
#### `output.write(chunk)`
|
|
304
|
-
|
|
305
|
-
Writes regular terminal output through stdout.
|
|
306
|
-
|
|
307
|
-
#### `output.error(chunk)`
|
|
308
|
-
|
|
309
|
-
Writes diagnostic terminal output through stderr.
|
|
310
|
-
|
|
311
|
-
#### `output.stdout.write(chunk)`
|
|
312
|
-
|
|
313
|
-
Writes directly to the stdout channel. Use this only when a specific channel
|
|
314
|
-
must be passed around.
|
|
315
|
-
|
|
316
|
-
#### `output.stderr.write(chunk)`
|
|
317
|
-
|
|
318
|
-
Writes directly to the stderr channel. Use this only when a specific channel
|
|
319
|
-
must be passed around.
|
|
320
|
-
|
|
321
|
-
### Lower-Level Mechanics
|
|
322
|
-
|
|
323
|
-
Lower-level mechanics sit behind the construction methods. They are kept out of
|
|
324
|
-
the main navigation to keep the application construction path first.
|
|
325
|
-
|
|
326
|
-
For application code, prefer `createTerminalApp()` first. Drop to this level
|
|
327
|
-
only when the terminal application flow or construction methods are too coarse.
|
|
328
|
-
|
|
329
|
-
Source of truth:
|
|
330
|
-
|
|
331
|
-
- [src/argv/parser.ts](src/argv/parser.ts) for raw argv parsing;
|
|
332
|
-
- [src/options/parser.ts](src/options/parser.ts) for option value validation;
|
|
333
|
-
- [src/command/mechanics.ts](src/command/mechanics.ts) for command resolution,
|
|
334
|
-
preparation, and execution.
|
|
335
|
-
|
|
336
|
-
#### Parsing And Resolution
|
|
337
|
-
|
|
338
|
-
This group turns raw arguments or positionals into a selected command.
|
|
339
|
-
|
|
340
|
-
- <a id="parseargv"></a>`parseArgv(args, schema?)` parses raw CLI arguments into positionals and raw
|
|
341
|
-
option values;
|
|
342
|
-
- <a id="resolvecommand"></a>`resolveCommand(registry, positionals)` resolves from already parsed
|
|
343
|
-
positionals;
|
|
344
|
-
- <a id="resolvecommandfromargs"></a>`resolveCommandFromArgs(registry, args)` resolves from raw CLI arguments by
|
|
345
|
-
using registered command schemas.
|
|
346
|
-
|
|
347
|
-
Use these methods when command selection is needed without validation or
|
|
348
|
-
execution.
|
|
349
|
-
|
|
350
|
-
#### Preparation
|
|
351
|
-
|
|
352
|
-
This group validates command input without runtime context.
|
|
353
|
-
|
|
354
|
-
- <a id="parseoptionsdetailed"></a>`parseOptionsDetailed(schema, values)` validates raw option values and keeps
|
|
355
|
-
option presence metadata;
|
|
356
|
-
- <a id="preparecommandfromargs"></a>`prepareCommandFromArgs(registry, args, options?)` resolves and validates a
|
|
357
|
-
registered command;
|
|
358
|
-
- `options.strict` rejects options before the command path.
|
|
359
|
-
|
|
360
|
-
Use preparation when runtime resources should be created only after command
|
|
361
|
-
selection is known.
|
|
362
|
-
|
|
363
|
-
#### Execution
|
|
364
|
-
|
|
365
|
-
This group calls command handlers after command input is prepared.
|
|
366
|
-
|
|
367
|
-
- <a id="runpreparedcommand"></a>`runPreparedCommand(prepared, context)` runs an already prepared command;
|
|
368
|
-
- <a id="runcommandfromregistry"></a>`runCommandFromRegistry(registry, args, context, options?)` prepares and runs
|
|
369
|
-
a command from a registry;
|
|
370
|
-
- <a id="runcommand"></a>`runCommand(command, args, context, options?)` runs a single command without a
|
|
371
|
-
registry.
|
|
372
|
-
|
|
373
|
-
Use execution primitives for custom boundaries. Regular CLI applications should
|
|
374
|
-
usually stay at `createTerminalApp()`.
|
|
375
|
-
|
|
376
|
-
## Internal Source Layout
|
|
377
|
-
|
|
378
|
-
The public package entrypoint remains `src/index.ts`. Internal source files are
|
|
379
|
-
grouped by CLI framework responsibility:
|
|
380
|
-
|
|
381
|
-
```text
|
|
382
|
-
src/
|
|
383
|
-
argv/ raw argv token parsing
|
|
384
|
-
options/ option schemas and option value validation
|
|
385
|
-
command/ command resolution and execution mechanics
|
|
386
|
-
presentation/ terminal view models and JSON/CSV/table renderers
|
|
387
|
-
output/ stdout/stderr writer boundaries
|
|
388
|
-
terminal/ command + presentation + output app composition
|
|
389
|
-
errors/ machine-readable icore errors
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
Consumers should continue importing from `icore`; deep imports are an internal
|
|
393
|
-
source layout detail.
|
|
394
|
-
|
|
395
|
-
## How It Works
|
|
396
|
-
|
|
397
|
-

|
|
398
|
-
|
|
399
|
-
## Example
|
|
400
|
-
|
|
401
|
-
More examples live in [examples/readme.md](examples/readme.md), including
|
|
402
|
-
option schemas, CLI argument syntax, practical CLI patterns, and lower-level
|
|
403
|
-
primitives.
|
|
404
|
-
|
|
405
|
-
```ts
|
|
406
|
-
import { defineCommand, runCommand } from 'icore';
|
|
407
|
-
|
|
408
|
-
const exampleCommand = defineCommand({
|
|
409
|
-
path: ['hello'],
|
|
410
|
-
options: {
|
|
411
|
-
name: {
|
|
412
|
-
type: 'string',
|
|
413
|
-
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
|
+
}
|
|
414
66
|
},
|
|
415
|
-
|
|
416
|
-
|
|
67
|
+
handle({ options }) {
|
|
68
|
+
const greeting = `Hello, ${options.name}!`;
|
|
69
|
+
|
|
70
|
+
return `${options.uppercase ? greeting.toUpperCase() : greeting}\n`;
|
|
417
71
|
}
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
const message = `Hello, ${options.name}!`;
|
|
72
|
+
})
|
|
73
|
+
] as const);
|
|
421
74
|
|
|
422
|
-
|
|
423
|
-
}
|
|
424
|
-
});
|
|
75
|
+
const app = createTerminalApp({ commands });
|
|
425
76
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
77
|
+
async function main(args: readonly string[]): Promise<void> {
|
|
78
|
+
process.exitCode = await app.run(args, undefined, {
|
|
79
|
+
strict: true
|
|
80
|
+
});
|
|
81
|
+
}
|
|
431
82
|
|
|
432
|
-
|
|
83
|
+
void main(process.argv.slice(2));
|
|
433
84
|
```
|
|
434
85
|
|
|
435
|
-
|
|
86
|
+
After compiling the application:
|
|
436
87
|
|
|
437
88
|
```console
|
|
438
|
-
$ node cli.js hello
|
|
439
|
-
|
|
440
|
-
```
|
|
441
|
-
|
|
442
|
-
The command handler receives parsed options, user-provided option metadata,
|
|
443
|
-
remaining positionals, prepared payload, and caller provided context.
|
|
444
|
-
|
|
445
|
-
## Option Schemas
|
|
446
|
-
|
|
447
|
-
Options are described as plain objects.
|
|
448
|
-
|
|
449
|
-
**Option names are exact.** `icore` does not normalize `camelCase` to
|
|
450
|
-
`kebab-case`. Use quoted object keys when your public CLI option contains
|
|
451
|
-
`-`.
|
|
452
|
-
|
|
453
|
-
Each option can define an optional short alias:
|
|
454
|
-
|
|
455
|
-
```ts
|
|
456
|
-
const schema = {
|
|
457
|
-
name: {
|
|
458
|
-
type: 'string',
|
|
459
|
-
alias: 'n'
|
|
460
|
-
},
|
|
461
|
-
uppercase: {
|
|
462
|
-
type: 'boolean',
|
|
463
|
-
alias: 'u'
|
|
464
|
-
}
|
|
465
|
-
} as const;
|
|
466
|
-
```
|
|
467
|
-
|
|
468
|
-
Aliases must be a single ASCII letter and unique within the schema. Parsed
|
|
469
|
-
values are always returned by long option name.
|
|
470
|
-
|
|
471
|
-
### `type: 'string'`
|
|
472
|
-
|
|
473
|
-
```ts
|
|
474
|
-
const schema = {
|
|
475
|
-
name: {
|
|
476
|
-
type: 'string',
|
|
477
|
-
required: true
|
|
478
|
-
},
|
|
479
|
-
style: {
|
|
480
|
-
type: 'string',
|
|
481
|
-
choices: ['short', 'long'],
|
|
482
|
-
default: 'short'
|
|
483
|
-
}
|
|
484
|
-
} as const;
|
|
485
|
-
```
|
|
486
|
-
|
|
487
|
-
String options reject missing required values, blank strings such as `--name=`,
|
|
488
|
-
boolean flag form, and values outside `choices`.
|
|
489
|
-
|
|
490
|
-
### `type: 'boolean'`
|
|
491
|
-
|
|
492
|
-
```ts
|
|
493
|
-
const schema = {
|
|
494
|
-
uppercase: {
|
|
495
|
-
type: 'boolean'
|
|
496
|
-
}
|
|
497
|
-
} as const;
|
|
498
|
-
```
|
|
89
|
+
$ node dist/cli.js hello
|
|
90
|
+
Hello, world!
|
|
499
91
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
```sh
|
|
503
|
-
--uppercase
|
|
504
|
-
--no-uppercase
|
|
505
|
-
```
|
|
506
|
-
|
|
507
|
-
Explicit values are rejected:
|
|
508
|
-
|
|
509
|
-
```sh
|
|
510
|
-
--uppercase=true
|
|
511
|
-
--uppercase=false
|
|
512
|
-
--uppercase=yes
|
|
513
|
-
--uppercase=
|
|
514
|
-
```
|
|
515
|
-
|
|
516
|
-
`--uppercase false` keeps `--uppercase` as `true` and leaves `false` as a positional
|
|
517
|
-
argument.
|
|
518
|
-
|
|
519
|
-
Use `syntax: 'flag'` when a boolean option should accept only flag form:
|
|
520
|
-
|
|
521
|
-
```ts
|
|
522
|
-
const schema = {
|
|
523
|
-
uppercase: {
|
|
524
|
-
type: 'boolean',
|
|
525
|
-
default: false,
|
|
526
|
-
syntax: 'flag'
|
|
527
|
-
}
|
|
528
|
-
} as const;
|
|
92
|
+
$ node dist/cli.js hello --name Alice --uppercase
|
|
93
|
+
HELLO, ALICE!
|
|
529
94
|
```
|
|
530
95
|
|
|
531
|
-
|
|
532
|
-
|
|
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`.
|
|
533
99
|
|
|
534
|
-
|
|
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.
|
|
535
102
|
|
|
536
|
-
|
|
537
|
-
const schema = {
|
|
538
|
-
limit: {
|
|
539
|
-
type: 'number',
|
|
540
|
-
integer: true,
|
|
541
|
-
min: 1,
|
|
542
|
-
max: 1000,
|
|
543
|
-
default: 100
|
|
544
|
-
}
|
|
545
|
-
} as const;
|
|
546
|
-
```
|
|
103
|
+
## Choose An API Level
|
|
547
104
|
|
|
548
|
-
|
|
549
|
-
constraints. Defaults are validated with the same rules as user-provided values.
|
|
105
|
+
Start with the highest-level API that fits the application:
|
|
550
106
|
|
|
551
|
-
|
|
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) |
|
|
552
115
|
|
|
553
|
-
|
|
116
|
+
The terminal application exposes the main lifecycle operations:
|
|
554
117
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
default: 'world'
|
|
562
|
-
},
|
|
563
|
-
uppercase: {
|
|
564
|
-
type: 'boolean'
|
|
565
|
-
}
|
|
566
|
-
} 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.
|
|
567
124
|
|
|
568
|
-
|
|
569
|
-
|
|
125
|
+
Command handlers receive parsed `options`, option-presence metadata in
|
|
126
|
+
`provided`, remaining `positionals`, caller-owned `context`, and an optional
|
|
127
|
+
prepared `payload`.
|
|
570
128
|
|
|
571
|
-
|
|
129
|
+
Supported terminal results are strings, async string streams, presentation
|
|
130
|
+
results, and `undefined`:
|
|
572
131
|
|
|
573
|
-
```
|
|
574
|
-
|
|
575
|
-
name: string;
|
|
576
|
-
uppercase: boolean | undefined;
|
|
577
|
-
};
|
|
132
|
+
```text
|
|
133
|
+
argv → resolve → validate/prepare → execute → render → stdout/stderr
|
|
578
134
|
```
|
|
579
135
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
Use `InferProvidedOptions` when you need the option presence type explicitly.
|
|
584
|
-
|
|
585
|
-
```ts
|
|
586
|
-
import type { InferProvidedOptions } from 'icore';
|
|
136
|
+
Exact public exports live in [`src/index.ts`](src/index.ts) and in the bundled
|
|
137
|
+
TypeScript declarations.
|
|
587
138
|
|
|
588
|
-
|
|
589
|
-
```
|
|
139
|
+
## Supported Argument Syntax
|
|
590
140
|
|
|
591
|
-
|
|
592
|
-
specified that option explicitly; defaults keep the flag `false`.
|
|
141
|
+
The supported syntax is intentionally small and predictable:
|
|
593
142
|
|
|
594
|
-
|
|
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 |
|
|
595
151
|
|
|
596
|
-
|
|
597
|
-
|
|
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.
|
|
598
155
|
|
|
599
|
-
|
|
600
|
-
|
|
156
|
+
See [CLI Argument Syntax](examples/cli-argument-syntax.md) for parsing examples,
|
|
157
|
+
edge cases, duplicate handling, and the option terminator contract.
|
|
601
158
|
|
|
602
|
-
|
|
159
|
+
## Error Handling
|
|
603
160
|
|
|
604
|
-
|
|
605
|
-
|
|
161
|
+
CLI parsing, validation, resolution, and definition failures are reported as
|
|
162
|
+
`IcoreError`. It extends `Error` with:
|
|
606
163
|
|
|
607
|
-
|
|
608
|
-
|
|
164
|
+
- a stable machine-readable `code`;
|
|
165
|
+
- a `usage` or `definition` category;
|
|
166
|
+
- required, code-specific `details`.
|
|
609
167
|
|
|
610
|
-
`
|
|
168
|
+
Use `isIcoreError(...)` instead of manually casting details. Passing a code to
|
|
169
|
+
the guard narrows the corresponding details shape:
|
|
611
170
|
|
|
612
171
|
```ts
|
|
613
|
-
|
|
614
|
-
|
|
172
|
+
import {
|
|
173
|
+
createTerminalApp,
|
|
174
|
+
isIcoreError
|
|
175
|
+
} from 'icore';
|
|
615
176
|
|
|
616
|
-
|
|
177
|
+
const help = 'Usage: cli hello [--name value]';
|
|
617
178
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
Attached short values such as `-nvalue` and grouped short booleans such as
|
|
627
|
-
`-abc` are not supported yet. Unknown short tokens remain positional for
|
|
628
|
-
compatibility.
|
|
629
|
-
|
|
630
|
-
Negated syntax such as `--no-cache` is interpreted as `cache: false` when
|
|
631
|
-
`cache` is a known boolean option without `syntax: 'flag'`. Unknown negated
|
|
632
|
-
options, negation for string or number options, and negation for flag-only
|
|
633
|
-
boolean options are rejected.
|
|
634
|
-
|
|
635
|
-
## Error Messages
|
|
636
|
-
|
|
637
|
-
`icore` throws `IcoreError` objects for CLI parsing, option validation, and
|
|
638
|
-
command resolution failures. `IcoreError` extends the regular `Error` class and
|
|
639
|
-
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);
|
|
640
186
|
|
|
641
|
-
|
|
642
|
-
|
|
187
|
+
if (isIcoreError(error, 'UNKNOWN_COMMAND')) {
|
|
188
|
+
return `${message}\n\n${help}\n`;
|
|
189
|
+
}
|
|
643
190
|
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
printHelp();
|
|
652
|
-
process.exitCode = 2;
|
|
653
|
-
return;
|
|
191
|
+
return `${message}\n`;
|
|
192
|
+
},
|
|
193
|
+
resolveExitCode(error) {
|
|
194
|
+
return isIcoreError(error) && error.category === 'usage'
|
|
195
|
+
? 2
|
|
196
|
+
: 1;
|
|
197
|
+
}
|
|
654
198
|
}
|
|
655
|
-
|
|
656
|
-
throw error;
|
|
657
|
-
}
|
|
199
|
+
});
|
|
658
200
|
```
|
|
659
201
|
|
|
660
|
-
|
|
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.
|
|
661
206
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
| 'UNEXPECTED_ARGUMENT'
|
|
666
|
-
| 'DUPLICATE_ARGUMENT'
|
|
667
|
-
| 'EXPECTED_REQUIRED_ARGUMENT'
|
|
668
|
-
| 'INVALID_OPTION_TYPE'
|
|
669
|
-
| 'INVALID_OPTION_CHOICE'
|
|
670
|
-
| 'UNEXPECTED_POSITIONAL'
|
|
671
|
-
| 'INVALID_OPTION_ALIAS'
|
|
672
|
-
| 'DUPLICATE_ALIAS'
|
|
673
|
-
| 'INVALID_OPTION_DEFAULT'
|
|
674
|
-
| 'DUPLICATE_COMMAND';
|
|
675
|
-
```
|
|
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.
|
|
676
210
|
|
|
677
|
-
|
|
678
|
-
|
|
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).
|
|
679
215
|
|
|
680
|
-
|
|
681
|
-
$ node cli.js hello --unknown
|
|
682
|
-
Unexpected argument '--unknown'
|
|
216
|
+
## Guides
|
|
683
217
|
|
|
684
|
-
|
|
685
|
-
|
|
218
|
+
The [examples index](examples/readme.md) routes from regular terminal apps to
|
|
219
|
+
lower-level mechanics. Useful starting points:
|
|
686
220
|
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
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.
|
|
690
233
|
|
|
691
|
-
|
|
692
|
-
|
|
234
|
+
Release history is recorded in [CHANGELOG.md](CHANGELOG.md). Directional
|
|
235
|
+
decisions live in [docs/roadmap.md](docs/roadmap.md).
|
|
693
236
|
|
|
694
237
|
## Project Boundary
|
|
695
238
|
|
|
696
|
-
`icore` is
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
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.
|
|
700
242
|
|
|
701
|
-
|
|
702
|
-
-
|
|
703
|
-
|
|
704
|
-
- typed command handler input;
|
|
705
|
-
- generic JSON, CSV, and text table rendering;
|
|
706
|
-
- 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.
|
|
707
246
|
|
|
708
|
-
|
|
709
|
-
loading, and domain behavior stay outside `icore`.
|
|
247
|
+
Licensed under the [MIT License](LICENSE).
|