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

|
|
413
396
|
|
|
414
|
-
|
|
415
397
|
## Example
|
|
416
398
|
|
|
399
|
+
More examples live in [examples/readme.md](examples/readme.md), including
|
|
400
|
+
option schemas, CLI argument syntax, practical CLI patterns, and lower-level
|
|
401
|
+
primitives.
|
|
402
|
+
|
|
417
403
|
```ts
|
|
418
404
|
import { defineCommand, runCommand } from 'icore';
|
|
419
405
|
|
|
@@ -713,4 +699,9 @@ Good responsibilities for `icore`:
|
|
|
713
699
|
- option schema evaluation;
|
|
714
700
|
- command path checking;
|
|
715
701
|
- common argument errors;
|
|
716
|
-
- typed command handler input
|
|
702
|
+
- typed command handler input;
|
|
703
|
+
- generic JSON, CSV, and text table rendering;
|
|
704
|
+
- stdout/stderr text writer mechanics.
|
|
705
|
+
|
|
706
|
+
Application-specific report mapping, scalar formatting, API calls, config
|
|
707
|
+
loading, and domain behavior stay outside `icore`.
|