icore 1.0.19 → 2.0.1
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 +104 -54
- package/dist/argv/parser.js +11 -4
- 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 +21 -12
- package/dist/options/schema.js +2 -1
- package/dist/output/text-writer.d.ts +3 -2
- package/dist/output/text-writer.js +42 -2
- package/dist/presentation/renderers/csv.d.ts +1 -1
- package/dist/presentation/renderers/csv.js +2 -2
- package/dist/presentation/renderers/json.d.ts +2 -0
- package/dist/presentation/renderers/json.js +7 -1
- package/dist/presentation/result-renderer.js +28 -3
- 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 +3 -2
- package/readme.md +170 -640
package/CHANGELOG.md
CHANGED
|
@@ -9,42 +9,94 @@ Historical entries before this file was introduced were reconstructed from npm
|
|
|
9
9
|
publish metadata and local git history. Older entries are intentionally
|
|
10
10
|
conservative.
|
|
11
11
|
|
|
12
|
-
Version boundaries
|
|
13
|
-
|
|
12
|
+
Version boundaries from `1.0.12` through `1.0.19` were checked against npm
|
|
13
|
+
`gitHead` metadata and local git history.
|
|
14
14
|
|
|
15
15
|
## [Unreleased]
|
|
16
16
|
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- Built package output automatically before npm and Yarn packing.
|
|
20
|
+
- Preserved option names that overlap properties inherited from
|
|
21
|
+
`Object.prototype`.
|
|
22
|
+
- Rejected malformed nested records, table rows, and CSV rows in presentation
|
|
23
|
+
result guards.
|
|
24
|
+
- Quoted CSV cells containing carriage returns.
|
|
25
|
+
- Rejected backpressured writes when EventEmitter-compatible sinks error or
|
|
26
|
+
close before `drain`.
|
|
27
|
+
- Rejected top-level values without a JSON representation instead of emitting
|
|
28
|
+
invalid JSON text.
|
|
29
|
+
|
|
30
|
+
## [2.0.0] - 2026-07-14
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- Added reusable terminal error rendering and exit-code policy with prepare,
|
|
35
|
+
execute, render, write, and external phase context.
|
|
36
|
+
- Added `TerminalApp.reportError(...)` for caller-owned lifecycle flows that
|
|
37
|
+
need the same stderr and exit-code behavior as `run(...)` and
|
|
38
|
+
`runPrepared(...)`.
|
|
39
|
+
- Added `IcoreError.category` to distinguish usage failures from invalid
|
|
40
|
+
command and option definitions.
|
|
41
|
+
- Added the public `IcoreErrorDetailsMap`, generic `IcoreErrorDetails<TCode>`,
|
|
42
|
+
and distributed `AnyIcoreError` contracts for code-specific error details.
|
|
43
|
+
- Added `isIcoreError(...)` overloads for narrowing any `IcoreError` or one
|
|
44
|
+
exact error code while preserving its details type.
|
|
45
|
+
|
|
46
|
+
### Changed
|
|
47
|
+
|
|
48
|
+
- Clarified that extra positionals after a matched command path are rejected
|
|
49
|
+
during prepare unless the command declares `allowExtraPositionals: true`.
|
|
50
|
+
- Changed `TerminalApp.run(...)` and `TerminalApp.runPrepared(...)` to share the
|
|
51
|
+
configured terminal error policy while preserving the default error text and
|
|
52
|
+
exit code.
|
|
53
|
+
- **Breaking:** changed the `IcoreError` constructor to require code-specific
|
|
54
|
+
`details`; direct callers must now provide the third argument.
|
|
55
|
+
- Added explicit variant discriminators to `UNKNOWN_COMMAND`,
|
|
56
|
+
`UNEXPECTED_ARGUMENT`, `EXPECTED_REQUIRED_ARGUMENT`, and
|
|
57
|
+
`INVALID_OPTION_DEFAULT` details.
|
|
58
|
+
|
|
59
|
+
## [1.0.19]
|
|
60
|
+
|
|
17
61
|
### Added
|
|
18
62
|
|
|
19
|
-
- Added `TerminalApp.runPrepared(...)` for running prepared commands through terminal rendering and output.
|
|
20
63
|
- Added `TerminalApp.writePreparedOutput(...)` for rendering and writing already obtained terminal output.
|
|
64
|
+
- Added `docs/roadmap.md` for directional project decisions that are not release history.
|
|
21
65
|
|
|
22
66
|
### Changed
|
|
23
67
|
|
|
24
|
-
- Changed `createTerminalApp` typing to accept command registries with void and prepared payloads.
|
|
25
68
|
- Changed `createTerminalApp` typing to allow custom command results when consumers run prepared commands themselves.
|
|
26
|
-
-
|
|
69
|
+
- Documented the caller-owned flow for command execution results that may be
|
|
70
|
+
lifecycle handles before terminal output is written.
|
|
71
|
+
- Documented that terminal string output is written exactly as provided.
|
|
27
72
|
|
|
28
|
-
## [1.0.
|
|
73
|
+
## [1.0.18]
|
|
29
74
|
|
|
30
75
|
### Added
|
|
31
76
|
|
|
32
|
-
- Added `
|
|
33
|
-
- Added semantic presentation facade: `createPresentation()`.
|
|
34
|
-
- Added semantic output facade: `createOutput()` with `output.write(...)` and `output.error(...)`.
|
|
35
|
-
- Added terminal-ready command output support: text, async text streams, presentation results, and empty output.
|
|
36
|
-
- Added guide-style examples for terminal app, command flow, option schemas, presentation, output, and lower-level mechanics.
|
|
77
|
+
- Added `TerminalApp.runPrepared(...)` for running prepared commands through terminal rendering and output.
|
|
37
78
|
|
|
38
79
|
### Changed
|
|
39
80
|
|
|
40
|
-
-
|
|
41
|
-
|
|
42
|
-
- Public README now positions `icore` as terminal application mechanics, not only argv parsing.
|
|
81
|
+
- Documented the `app.prepare(...)` and `app.runPrepared(...)` flow for applications
|
|
82
|
+
that create and clean up runtime context themselves.
|
|
43
83
|
|
|
44
|
-
## [1.0.
|
|
84
|
+
## [1.0.17]
|
|
85
|
+
|
|
86
|
+
### Changed
|
|
87
|
+
|
|
88
|
+
- Relaxed `createTerminalApp` typing so command registries with commands
|
|
89
|
+
without `prepare()` hooks and with void payloads compile without adapter casts.
|
|
90
|
+
|
|
91
|
+
## [1.0.16]
|
|
45
92
|
|
|
46
93
|
### Added
|
|
47
94
|
|
|
95
|
+
- Added `createTerminalApp()` as the top-level terminal application composition API.
|
|
96
|
+
- Added command mechanics facade: `createCommand()`.
|
|
97
|
+
- Added semantic presentation facade: `createPresentation()`.
|
|
98
|
+
- Added semantic output facade: `createOutput()` with `output.write(...)` and `output.error(...)`.
|
|
99
|
+
- Added terminal-ready command output support: text, async text streams, presentation results, and empty output.
|
|
48
100
|
- Added presentation primitives and renderers:
|
|
49
101
|
- `renderJson`
|
|
50
102
|
- `renderCsv`
|
|
@@ -56,67 +108,62 @@ verified against git tags.
|
|
|
56
108
|
- `createStdoutWriter`
|
|
57
109
|
- `createStderrWriter`
|
|
58
110
|
- `createBackpressureTextWriter`
|
|
111
|
+
- Added typed option subset parsing with `parseOptionsSubsetDetailed`.
|
|
112
|
+
- Added guide-style examples for terminal app, command flow, option schemas, presentation, output, and lower-level mechanics.
|
|
59
113
|
|
|
60
114
|
### Changed
|
|
61
115
|
|
|
62
|
-
-
|
|
116
|
+
- Documentation was reorganized around `command`, `presentation`, and `output`.
|
|
117
|
+
- Examples were grouped by usage level: Terminal Application, Layer Toolkit, Primitive Mechanics.
|
|
118
|
+
- Public README now positions `icore` as terminal application mechanics, not only argv parsing.
|
|
119
|
+
- Source files were reorganized by framework area: argv, command, options,
|
|
120
|
+
presentation, output, and terminal app.
|
|
121
|
+
- Updated tests to run with `fwa --prune`.
|
|
122
|
+
- Restored `CHANGELOG.md` to published package contents.
|
|
63
123
|
|
|
64
|
-
|
|
124
|
+
### Removed
|
|
125
|
+
|
|
126
|
+
- Removed the redundant CLI barrel module.
|
|
127
|
+
- Removed the legacy `git-flow.md` document.
|
|
128
|
+
- Removed the `prepare` package script.
|
|
129
|
+
|
|
130
|
+
## [1.0.15]
|
|
65
131
|
|
|
66
132
|
### Added
|
|
67
133
|
|
|
68
|
-
- Added command
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
- Added lower-level command primitives:
|
|
76
|
-
- `defineCommand`
|
|
77
|
-
- `defineCommandRegistry`
|
|
78
|
-
- `createCommands`
|
|
79
|
-
- `resolveCommand`
|
|
80
|
-
- `prepareCommandFromArgs`
|
|
81
|
-
- `runPreparedCommand`
|
|
82
|
-
- `runCommandFromRegistry`
|
|
83
|
-
- `runCommand`
|
|
134
|
+
- Added `isPreparedCommandName` for narrowing prepared command unions by command name.
|
|
135
|
+
|
|
136
|
+
### Changed
|
|
137
|
+
|
|
138
|
+
- Temporarily excluded `CHANGELOG.md` from published package contents.
|
|
139
|
+
|
|
140
|
+
## [1.0.14]
|
|
84
141
|
|
|
85
142
|
### Changed
|
|
86
143
|
|
|
87
|
-
-
|
|
144
|
+
- Preserved prepared command payload correlation when narrowing registry unions.
|
|
145
|
+
- Reformatted `CHANGELOG.md` according to Keep a Changelog.
|
|
88
146
|
|
|
89
147
|
## [1.0.13]
|
|
90
148
|
|
|
91
149
|
### Added
|
|
92
150
|
|
|
93
|
-
- Added
|
|
94
|
-
- Added
|
|
95
|
-
- Added
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
- Added detailed option parsing contracts for cases where caller needs explicit provided/not-provided metadata.
|
|
151
|
+
- Added machine-readable `IcoreError` codes and details.
|
|
152
|
+
- Added strict command resolution mode with `strict: true`.
|
|
153
|
+
- Added strict mode support to direct `runCommand(...)` calls.
|
|
154
|
+
- Added typed prepared command payloads.
|
|
155
|
+
- Added `CHANGELOG.md`.
|
|
99
156
|
|
|
100
157
|
### Changed
|
|
101
158
|
|
|
102
|
-
- Boolean option handling was tightened
|
|
159
|
+
- Boolean option handling was tightened to flag syntax only.
|
|
103
160
|
|
|
104
161
|
## [1.0.12]
|
|
105
162
|
|
|
106
|
-
###
|
|
163
|
+
### Changed
|
|
107
164
|
|
|
108
|
-
-
|
|
109
|
-
|
|
110
|
-
- `type: 'boolean'`
|
|
111
|
-
- `type: 'number'`
|
|
112
|
-
- Added GNU-style argv parsing:
|
|
113
|
-
- `--name value`
|
|
114
|
-
- `--name=value`
|
|
115
|
-
- `--flag`
|
|
116
|
-
- short aliases
|
|
117
|
-
- `--` terminator
|
|
118
|
-
- Added machine-readable `IcoreError`.
|
|
119
|
-
- Added initial TypeScript-first public API for command-line mechanics.
|
|
165
|
+
- Replaced the preliminary `flagOnly` boolean option setting with typed
|
|
166
|
+
`syntax: 'flag'`.
|
|
120
167
|
|
|
121
168
|
## [1.0.11] - 2026-07-02
|
|
122
169
|
|
|
@@ -268,6 +315,9 @@ verified against git tags.
|
|
|
268
315
|
- Detailed changelog entries were not maintained for these releases.
|
|
269
316
|
|
|
270
317
|
[Unreleased]: https://github.com/woodger/icore/commits/develop
|
|
318
|
+
[1.0.19]: https://www.npmjs.com/package/icore/v/1.0.19
|
|
319
|
+
[1.0.18]: https://www.npmjs.com/package/icore/v/1.0.18
|
|
320
|
+
[1.0.17]: https://www.npmjs.com/package/icore/v/1.0.17
|
|
271
321
|
[1.0.16]: https://www.npmjs.com/package/icore/v/1.0.16
|
|
272
322
|
[1.0.15]: https://www.npmjs.com/package/icore/v/1.0.15
|
|
273
323
|
[1.0.14]: https://www.npmjs.com/package/icore/v/1.0.14
|
package/dist/argv/parser.js
CHANGED
|
@@ -18,7 +18,7 @@ const icore_error_1 = require("../errors/icore-error");
|
|
|
18
18
|
*/
|
|
19
19
|
function parseArgv(args, schema) {
|
|
20
20
|
const positionals = [];
|
|
21
|
-
const options =
|
|
21
|
+
const options = Object.create(null);
|
|
22
22
|
const aliases = buildShortAliasMap(schema);
|
|
23
23
|
let parseOptions = true;
|
|
24
24
|
for (let index = 0; index < args.length; index += 1) {
|
|
@@ -68,10 +68,10 @@ function parseArgv(args, schema) {
|
|
|
68
68
|
if (name === '') {
|
|
69
69
|
throw createUnexpectedArgumentError(arg);
|
|
70
70
|
}
|
|
71
|
-
const definition = schema
|
|
71
|
+
const definition = getOwnOptionDefinition(schema, name);
|
|
72
72
|
if (separatorIndex === -1 && definition === undefined && name.startsWith('no-')) {
|
|
73
73
|
const negatedName = name.slice(3);
|
|
74
|
-
const negatedDefinition = schema
|
|
74
|
+
const negatedDefinition = getOwnOptionDefinition(schema, negatedName);
|
|
75
75
|
if (negatedDefinition?.type === 'boolean') {
|
|
76
76
|
if (Object.hasOwn(options, negatedName)) {
|
|
77
77
|
throw createDuplicateArgumentError(negatedName);
|
|
@@ -106,9 +106,15 @@ function parseArgv(args, schema) {
|
|
|
106
106
|
}
|
|
107
107
|
return {
|
|
108
108
|
positionals,
|
|
109
|
-
options
|
|
109
|
+
options: { ...options }
|
|
110
110
|
};
|
|
111
111
|
}
|
|
112
|
+
function getOwnOptionDefinition(schema, name) {
|
|
113
|
+
if (schema === undefined || !Object.hasOwn(schema, name)) {
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
return schema[name];
|
|
117
|
+
}
|
|
112
118
|
function buildShortAliasMap(schema) {
|
|
113
119
|
const aliases = new Map();
|
|
114
120
|
if (schema === undefined) {
|
|
@@ -147,6 +153,7 @@ function createDuplicateArgumentError(name) {
|
|
|
147
153
|
}
|
|
148
154
|
function createUnexpectedArgumentError(argument) {
|
|
149
155
|
return new icore_error_1.IcoreError('UNEXPECTED_ARGUMENT', `Unexpected argument '${argument}'`, {
|
|
156
|
+
reason: 'malformed-option',
|
|
150
157
|
argument
|
|
151
158
|
});
|
|
152
159
|
}
|
|
@@ -220,6 +220,7 @@ function resolveCommandPositionals(path, positionals) {
|
|
|
220
220
|
if (positionals[index] !== path[index]) {
|
|
221
221
|
const command = path.join(' ');
|
|
222
222
|
throw new icore_error_1.IcoreError('UNKNOWN_COMMAND', `Expected command '${command}'`, {
|
|
223
|
+
reason: 'path-mismatch',
|
|
223
224
|
command,
|
|
224
225
|
path: [...path],
|
|
225
226
|
positionals: [...positionals]
|
|
@@ -305,12 +306,14 @@ function isOptionBeforeCommand(arg) {
|
|
|
305
306
|
function createUnknownCommandError(positionals) {
|
|
306
307
|
const command = formatCommandPositionals(positionals);
|
|
307
308
|
return new icore_error_1.IcoreError('UNKNOWN_COMMAND', `Unknown command: ${command}`, {
|
|
309
|
+
reason: 'unresolved',
|
|
308
310
|
command,
|
|
309
311
|
positionals: [...positionals]
|
|
310
312
|
});
|
|
311
313
|
}
|
|
312
314
|
function createUnexpectedArgumentError(argument) {
|
|
313
315
|
return new icore_error_1.IcoreError('UNEXPECTED_ARGUMENT', `Unexpected argument '${argument}'`, {
|
|
316
|
+
reason: 'option-before-command',
|
|
314
317
|
argument
|
|
315
318
|
});
|
|
316
319
|
}
|
|
@@ -3,27 +3,150 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Allowed here:
|
|
5
5
|
* - defining stable error codes;
|
|
6
|
+
* - correlating error codes with structured details;
|
|
6
7
|
* - preserving human-readable messages on Error instances;
|
|
7
8
|
* - carrying structured details for application-level handling;
|
|
9
|
+
* - narrowing unknown errors by their stable codes;
|
|
8
10
|
*
|
|
9
11
|
* This file must not contain parser, validator, or command resolution logic.
|
|
10
12
|
*/
|
|
13
|
+
type OptionIdentityDetails = {
|
|
14
|
+
argument: string;
|
|
15
|
+
option: string;
|
|
16
|
+
};
|
|
17
|
+
type UnknownCommandDetails = {
|
|
18
|
+
reason: 'unresolved';
|
|
19
|
+
command: string;
|
|
20
|
+
positionals: readonly string[];
|
|
21
|
+
} | {
|
|
22
|
+
reason: 'path-mismatch';
|
|
23
|
+
command: string;
|
|
24
|
+
path: readonly string[];
|
|
25
|
+
positionals: readonly string[];
|
|
26
|
+
};
|
|
27
|
+
type UnexpectedArgumentDetails = {
|
|
28
|
+
reason: 'malformed-option';
|
|
29
|
+
argument: string;
|
|
30
|
+
} | {
|
|
31
|
+
reason: 'unknown-option';
|
|
32
|
+
argument: string;
|
|
33
|
+
option: string;
|
|
34
|
+
} | {
|
|
35
|
+
reason: 'option-before-command';
|
|
36
|
+
argument: string;
|
|
37
|
+
};
|
|
38
|
+
type ExpectedRequiredArgumentDetails = {
|
|
39
|
+
reason: 'option';
|
|
40
|
+
argument: string;
|
|
41
|
+
option: string;
|
|
42
|
+
} | {
|
|
43
|
+
reason: 'positional';
|
|
44
|
+
argument: string;
|
|
45
|
+
positional: string;
|
|
46
|
+
};
|
|
47
|
+
type InvalidOptionTypeDetails = OptionIdentityDetails & ({
|
|
48
|
+
expected: 'string' | 'boolean flag' | 'number';
|
|
49
|
+
value: unknown;
|
|
50
|
+
} | {
|
|
51
|
+
expected: 'integer';
|
|
52
|
+
value: number;
|
|
53
|
+
} | {
|
|
54
|
+
expected: 'minimum';
|
|
55
|
+
min: number;
|
|
56
|
+
value: number;
|
|
57
|
+
} | {
|
|
58
|
+
expected: 'maximum';
|
|
59
|
+
max: number;
|
|
60
|
+
value: number;
|
|
61
|
+
});
|
|
62
|
+
type InvalidOptionDefaultDetails = OptionIdentityDetails & ({
|
|
63
|
+
expected: 'string' | 'boolean' | 'number';
|
|
64
|
+
value: unknown;
|
|
65
|
+
} | {
|
|
66
|
+
expected: 'integer';
|
|
67
|
+
value: number;
|
|
68
|
+
} | {
|
|
69
|
+
expected: 'minimum';
|
|
70
|
+
min: number;
|
|
71
|
+
value: number;
|
|
72
|
+
} | {
|
|
73
|
+
expected: 'maximum';
|
|
74
|
+
max: number;
|
|
75
|
+
value: number;
|
|
76
|
+
} | {
|
|
77
|
+
expected: 'choice';
|
|
78
|
+
choices: readonly (string | number)[];
|
|
79
|
+
value: string | number;
|
|
80
|
+
});
|
|
81
|
+
/** Structured details protocol keyed by every stable `IcoreError` code. */
|
|
82
|
+
export type IcoreErrorDetailsMap = {
|
|
83
|
+
UNKNOWN_COMMAND: UnknownCommandDetails;
|
|
84
|
+
UNEXPECTED_ARGUMENT: UnexpectedArgumentDetails;
|
|
85
|
+
DUPLICATE_ARGUMENT: OptionIdentityDetails;
|
|
86
|
+
EXPECTED_REQUIRED_ARGUMENT: ExpectedRequiredArgumentDetails;
|
|
87
|
+
INVALID_OPTION_TYPE: InvalidOptionTypeDetails;
|
|
88
|
+
INVALID_OPTION_CHOICE: OptionIdentityDetails & {
|
|
89
|
+
choices: readonly (string | number)[];
|
|
90
|
+
value: string | number;
|
|
91
|
+
};
|
|
92
|
+
UNEXPECTED_POSITIONAL: {
|
|
93
|
+
command: string;
|
|
94
|
+
positional: string;
|
|
95
|
+
positionals: readonly string[];
|
|
96
|
+
};
|
|
97
|
+
INVALID_OPTION_ALIAS: OptionIdentityDetails & {
|
|
98
|
+
alias: unknown;
|
|
99
|
+
};
|
|
100
|
+
DUPLICATE_ALIAS: {
|
|
101
|
+
argument: string;
|
|
102
|
+
alias: string;
|
|
103
|
+
};
|
|
104
|
+
INVALID_OPTION_DEFAULT: InvalidOptionDefaultDetails;
|
|
105
|
+
DUPLICATE_COMMAND: {
|
|
106
|
+
command: string;
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
/** Stable machine-readable error code for `icore` CLI mechanics errors. */
|
|
110
|
+
export type IcoreErrorCode = keyof IcoreErrorDetailsMap;
|
|
111
|
+
/** Structured details for one exact `IcoreError` code. */
|
|
112
|
+
export type IcoreErrorDetails<TCode extends IcoreErrorCode> = Readonly<IcoreErrorDetailsMap[TCode]>;
|
|
11
113
|
/**
|
|
12
|
-
* Stable
|
|
114
|
+
* Stable category for distinguishing user input failures from invalid command
|
|
115
|
+
* or option definitions.
|
|
13
116
|
*/
|
|
14
|
-
export type
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
117
|
+
export type IcoreErrorCategory = 'usage' | 'definition';
|
|
118
|
+
declare const icoreErrorCategoryByCode: {
|
|
119
|
+
readonly UNKNOWN_COMMAND: "usage";
|
|
120
|
+
readonly UNEXPECTED_ARGUMENT: "usage";
|
|
121
|
+
readonly DUPLICATE_ARGUMENT: "usage";
|
|
122
|
+
readonly EXPECTED_REQUIRED_ARGUMENT: "usage";
|
|
123
|
+
readonly INVALID_OPTION_TYPE: "usage";
|
|
124
|
+
readonly INVALID_OPTION_CHOICE: "usage";
|
|
125
|
+
readonly UNEXPECTED_POSITIONAL: "usage";
|
|
126
|
+
readonly INVALID_OPTION_ALIAS: "definition";
|
|
127
|
+
readonly DUPLICATE_ALIAS: "definition";
|
|
128
|
+
readonly INVALID_OPTION_DEFAULT: "definition";
|
|
129
|
+
readonly DUPLICATE_COMMAND: "definition";
|
|
130
|
+
};
|
|
19
131
|
/**
|
|
20
132
|
* Error thrown by `icore` for CLI parsing, option validation, command
|
|
21
133
|
* resolution, and schema configuration failures.
|
|
22
134
|
*/
|
|
23
|
-
export declare class IcoreError extends Error {
|
|
135
|
+
export declare class IcoreError<TCode extends IcoreErrorCode = IcoreErrorCode> extends Error {
|
|
24
136
|
/** Stable machine-readable code. */
|
|
25
|
-
readonly code:
|
|
137
|
+
readonly code: TCode;
|
|
138
|
+
/** Stable high-level error category. */
|
|
139
|
+
readonly category: typeof icoreErrorCategoryByCode[TCode];
|
|
26
140
|
/** Structured error context. */
|
|
27
|
-
readonly details: IcoreErrorDetails
|
|
28
|
-
constructor(code:
|
|
141
|
+
readonly details: IcoreErrorDetails<TCode>;
|
|
142
|
+
constructor(code: TCode, message: string, details: IcoreErrorDetails<TCode>);
|
|
29
143
|
}
|
|
144
|
+
/** Distributed union preserving the correlation between code and details. */
|
|
145
|
+
export type AnyIcoreError = {
|
|
146
|
+
[TCode in IcoreErrorCode]: IcoreError<TCode>;
|
|
147
|
+
}[IcoreErrorCode];
|
|
148
|
+
/** Checks whether an unknown value is any supported `IcoreError`. */
|
|
149
|
+
export declare function isIcoreError(error: unknown): error is AnyIcoreError;
|
|
150
|
+
/** Checks and narrows an unknown value to one exact `IcoreError` code. */
|
|
151
|
+
export declare function isIcoreError<TCode extends IcoreErrorCode>(error: unknown, code: TCode): error is IcoreError<TCode>;
|
|
152
|
+
export {};
|
|
@@ -4,13 +4,29 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Allowed here:
|
|
6
6
|
* - defining stable error codes;
|
|
7
|
+
* - correlating error codes with structured details;
|
|
7
8
|
* - preserving human-readable messages on Error instances;
|
|
8
9
|
* - carrying structured details for application-level handling;
|
|
10
|
+
* - narrowing unknown errors by their stable codes;
|
|
9
11
|
*
|
|
10
12
|
* This file must not contain parser, validator, or command resolution logic.
|
|
11
13
|
*/
|
|
12
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
15
|
exports.IcoreError = void 0;
|
|
16
|
+
exports.isIcoreError = isIcoreError;
|
|
17
|
+
const icoreErrorCategoryByCode = {
|
|
18
|
+
UNKNOWN_COMMAND: 'usage',
|
|
19
|
+
UNEXPECTED_ARGUMENT: 'usage',
|
|
20
|
+
DUPLICATE_ARGUMENT: 'usage',
|
|
21
|
+
EXPECTED_REQUIRED_ARGUMENT: 'usage',
|
|
22
|
+
INVALID_OPTION_TYPE: 'usage',
|
|
23
|
+
INVALID_OPTION_CHOICE: 'usage',
|
|
24
|
+
UNEXPECTED_POSITIONAL: 'usage',
|
|
25
|
+
INVALID_OPTION_ALIAS: 'definition',
|
|
26
|
+
DUPLICATE_ALIAS: 'definition',
|
|
27
|
+
INVALID_OPTION_DEFAULT: 'definition',
|
|
28
|
+
DUPLICATE_COMMAND: 'definition'
|
|
29
|
+
};
|
|
14
30
|
/**
|
|
15
31
|
* Error thrown by `icore` for CLI parsing, option validation, command
|
|
16
32
|
* resolution, and schema configuration failures.
|
|
@@ -18,14 +34,21 @@ exports.IcoreError = void 0;
|
|
|
18
34
|
class IcoreError extends Error {
|
|
19
35
|
/** Stable machine-readable code. */
|
|
20
36
|
code;
|
|
37
|
+
/** Stable high-level error category. */
|
|
38
|
+
category;
|
|
21
39
|
/** Structured error context. */
|
|
22
40
|
details;
|
|
23
|
-
constructor(code, message, details
|
|
41
|
+
constructor(code, message, details) {
|
|
24
42
|
super(message);
|
|
25
43
|
this.name = 'IcoreError';
|
|
26
44
|
this.code = code;
|
|
45
|
+
this.category = icoreErrorCategoryByCode[code];
|
|
27
46
|
this.details = details;
|
|
28
47
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
29
48
|
}
|
|
30
49
|
}
|
|
31
50
|
exports.IcoreError = IcoreError;
|
|
51
|
+
function isIcoreError(error, code) {
|
|
52
|
+
return error instanceof IcoreError
|
|
53
|
+
&& (code === undefined || error.code === code);
|
|
54
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
export { parseArgv, type ParsedArgv } from './argv/parser';
|
|
14
14
|
export { createCommand, createCommands, defineCommand, defineCommandRegistry, isCommandName, isPreparedCommandName, prepareCommandFromArgs, resolveCommand, resolveCommandFromArgs, runCommand, runPreparedCommand, runCommandFromRegistry, type Command, type CommandDefinition, type CommandContext, type CommandInput, type CommandName, type CommandPayload, type CommandRegistry, type CommandResolutionOptions, type CommandResult, type Commands, type PreparedCommand, type PreparedCommandInput, type ResolvedCommand } from './command/mechanics';
|
|
15
|
-
export { IcoreError, type IcoreErrorCode, type IcoreErrorDetails } from './errors/icore-error';
|
|
15
|
+
export { IcoreError, isIcoreError, type AnyIcoreError, type IcoreErrorCategory, type IcoreErrorCode, type IcoreErrorDetails, type IcoreErrorDetailsMap } from './errors/icore-error';
|
|
16
16
|
export { parseOptions, parseOptionsDetailed, parseOptionsSubsetDetailed, type ParseOptionsResult, type ParseOptionsSubsetResult } from './options/parser';
|
|
17
17
|
export { mergeOptionsSchema, type BooleanOption, type InferOptions, type InferProvidedOptions, type MergeOptionsSchemas, type NumberOption, type OptionDefinition, type OptionsSchema, type RawOptionValue, type StringOption } from './options/schema';
|
|
18
18
|
export { createPresentation, type Presentation, type PresentationRenderers } from './presentation/facade';
|
|
@@ -25,4 +25,4 @@ export { type CsvCell, type CsvRow, type CsvPresentationView, type EmptyPresenta
|
|
|
25
25
|
export { createOutput, type Output, type OutputOptions } from './output/facade';
|
|
26
26
|
export { createStderrWriter, createStdoutWriter } from './output/node-writer';
|
|
27
27
|
export { createBackpressureTextWriter, type BackpressureTextSink, type TextWriter } from './output/text-writer';
|
|
28
|
-
export { createTerminalApp, type TerminalApp, type TerminalAppOptions, type TerminalCommandOutput } from './terminal/app';
|
|
28
|
+
export { createTerminalApp, type TerminalApp, type TerminalAppOptions, type TerminalCommandOutput, type TerminalErrorContext, type TerminalErrorPhase, type TerminalErrorPolicy } from './terminal/app';
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* This file must not contain parser, validator, or command runtime logic.
|
|
13
13
|
*/
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.createTerminalApp = exports.createBackpressureTextWriter = exports.createStdoutWriter = exports.createStderrWriter = exports.createOutput = exports.renderPresentationResult = exports.isPresentationResult = exports.renderTextTable = exports.renderJson = exports.renderCsvRow = exports.renderCsv = exports.presentationFormats = exports.presentationFormatOptions = exports.isPresentationFormat = exports.createPresentation = exports.mergeOptionsSchema = exports.parseOptionsSubsetDetailed = exports.parseOptionsDetailed = exports.parseOptions = exports.IcoreError = exports.runCommandFromRegistry = exports.runPreparedCommand = exports.runCommand = exports.resolveCommandFromArgs = exports.resolveCommand = exports.prepareCommandFromArgs = exports.isPreparedCommandName = exports.isCommandName = exports.defineCommandRegistry = exports.defineCommand = exports.createCommands = exports.createCommand = exports.parseArgv = void 0;
|
|
15
|
+
exports.createTerminalApp = exports.createBackpressureTextWriter = exports.createStdoutWriter = exports.createStderrWriter = exports.createOutput = exports.renderPresentationResult = exports.isPresentationResult = exports.renderTextTable = exports.renderJson = exports.renderCsvRow = exports.renderCsv = exports.presentationFormats = exports.presentationFormatOptions = exports.isPresentationFormat = exports.createPresentation = exports.mergeOptionsSchema = exports.parseOptionsSubsetDetailed = exports.parseOptionsDetailed = exports.parseOptions = exports.isIcoreError = exports.IcoreError = exports.runCommandFromRegistry = exports.runPreparedCommand = exports.runCommand = exports.resolveCommandFromArgs = exports.resolveCommand = exports.prepareCommandFromArgs = exports.isPreparedCommandName = exports.isCommandName = exports.defineCommandRegistry = exports.defineCommand = exports.createCommands = exports.createCommand = exports.parseArgv = void 0;
|
|
16
16
|
var parser_1 = require("./argv/parser");
|
|
17
17
|
Object.defineProperty(exports, "parseArgv", { enumerable: true, get: function () { return parser_1.parseArgv; } });
|
|
18
18
|
var mechanics_1 = require("./command/mechanics");
|
|
@@ -30,6 +30,7 @@ Object.defineProperty(exports, "runPreparedCommand", { enumerable: true, get: fu
|
|
|
30
30
|
Object.defineProperty(exports, "runCommandFromRegistry", { enumerable: true, get: function () { return mechanics_1.runCommandFromRegistry; } });
|
|
31
31
|
var icore_error_1 = require("./errors/icore-error");
|
|
32
32
|
Object.defineProperty(exports, "IcoreError", { enumerable: true, get: function () { return icore_error_1.IcoreError; } });
|
|
33
|
+
Object.defineProperty(exports, "isIcoreError", { enumerable: true, get: function () { return icore_error_1.isIcoreError; } });
|
|
33
34
|
var parser_2 = require("./options/parser");
|
|
34
35
|
Object.defineProperty(exports, "parseOptions", { enumerable: true, get: function () { return parser_2.parseOptions; } });
|
|
35
36
|
Object.defineProperty(exports, "parseOptionsDetailed", { enumerable: true, get: function () { return parser_2.parseOptionsDetailed; } });
|
package/dist/options/parser.js
CHANGED
|
@@ -26,8 +26,8 @@ function parseOptions(schema, values) {
|
|
|
26
26
|
* metadata.
|
|
27
27
|
*/
|
|
28
28
|
function parseOptionsDetailed(schema, values) {
|
|
29
|
-
const parsed =
|
|
30
|
-
const provided =
|
|
29
|
+
const parsed = Object.create(null);
|
|
30
|
+
const provided = Object.create(null);
|
|
31
31
|
for (const name of Object.keys(values)) {
|
|
32
32
|
if (!Object.hasOwn(schema, name)) {
|
|
33
33
|
throw createUnexpectedArgumentError(name);
|
|
@@ -35,27 +35,30 @@ function parseOptionsDetailed(schema, values) {
|
|
|
35
35
|
}
|
|
36
36
|
for (const name of Object.keys(schema)) {
|
|
37
37
|
const definition = schema[name];
|
|
38
|
-
const
|
|
38
|
+
const optionName = String(name);
|
|
39
|
+
const value = Object.hasOwn(values, optionName)
|
|
40
|
+
? values[optionName]
|
|
41
|
+
: undefined;
|
|
39
42
|
if (definition === undefined) {
|
|
40
43
|
continue;
|
|
41
44
|
}
|
|
42
45
|
provided[name] = value !== undefined;
|
|
43
46
|
if (value === undefined) {
|
|
44
47
|
if ('default' in definition) {
|
|
45
|
-
parsed[name] = parseDefaultOptionValue(
|
|
48
|
+
parsed[name] = parseDefaultOptionValue(optionName, definition);
|
|
46
49
|
continue;
|
|
47
50
|
}
|
|
48
51
|
if (definition.required === true) {
|
|
49
|
-
throw createExpectedRequiredArgumentError(
|
|
52
|
+
throw createExpectedRequiredArgumentError(optionName);
|
|
50
53
|
}
|
|
51
54
|
parsed[name] = undefined;
|
|
52
55
|
continue;
|
|
53
56
|
}
|
|
54
|
-
parsed[name] = parseOptionValue(
|
|
57
|
+
parsed[name] = parseOptionValue(optionName, definition, value);
|
|
55
58
|
}
|
|
56
59
|
return {
|
|
57
|
-
options: parsed,
|
|
58
|
-
provided: provided
|
|
60
|
+
options: { ...parsed },
|
|
61
|
+
provided: { ...provided }
|
|
59
62
|
};
|
|
60
63
|
}
|
|
61
64
|
/**
|
|
@@ -63,8 +66,8 @@ function parseOptionsDetailed(schema, values) {
|
|
|
63
66
|
* remaining raw options untouched.
|
|
64
67
|
*/
|
|
65
68
|
function parseOptionsSubsetDetailed(schema, values) {
|
|
66
|
-
const subsetValues =
|
|
67
|
-
const rest =
|
|
69
|
+
const subsetValues = Object.create(null);
|
|
70
|
+
const rest = Object.create(null);
|
|
68
71
|
for (const [name, value] of Object.entries(values)) {
|
|
69
72
|
if (Object.hasOwn(schema, name)) {
|
|
70
73
|
subsetValues[name] = value;
|
|
@@ -74,7 +77,7 @@ function parseOptionsSubsetDetailed(schema, values) {
|
|
|
74
77
|
}
|
|
75
78
|
return {
|
|
76
79
|
...parseOptionsDetailed(schema, subsetValues),
|
|
77
|
-
rest
|
|
80
|
+
rest: { ...rest }
|
|
78
81
|
};
|
|
79
82
|
}
|
|
80
83
|
function parseOptionValue(name, definition, value) {
|
|
@@ -151,7 +154,10 @@ function validateNumberConstraints(name, definition, parsed, source = 'value') {
|
|
|
151
154
|
value: parsed
|
|
152
155
|
});
|
|
153
156
|
}
|
|
154
|
-
throw
|
|
157
|
+
throw createInvalidOptionTypeError(name, `Expected '--${name}' as integer`, {
|
|
158
|
+
expected: 'integer',
|
|
159
|
+
value: parsed
|
|
160
|
+
});
|
|
155
161
|
}
|
|
156
162
|
if (definition.min !== undefined && parsed < definition.min) {
|
|
157
163
|
const message = `Expected '--${name}' to be greater than or equal to ${String(definition.min)}`;
|
|
@@ -192,6 +198,7 @@ function assertChoice(name, choices, value, source = 'value') {
|
|
|
192
198
|
const message = `Expected '--${name}' as one of: ${choices.join(', ')}`;
|
|
193
199
|
if (source === 'default') {
|
|
194
200
|
throw createInvalidOptionDefaultError(name, message, {
|
|
201
|
+
expected: 'choice',
|
|
195
202
|
choices: [...choices],
|
|
196
203
|
value
|
|
197
204
|
});
|
|
@@ -200,12 +207,14 @@ function assertChoice(name, choices, value, source = 'value') {
|
|
|
200
207
|
}
|
|
201
208
|
function createUnexpectedArgumentError(name) {
|
|
202
209
|
return new icore_error_1.IcoreError('UNEXPECTED_ARGUMENT', `Unexpected argument '--${name}'`, {
|
|
210
|
+
reason: 'unknown-option',
|
|
203
211
|
argument: `--${name}`,
|
|
204
212
|
option: name
|
|
205
213
|
});
|
|
206
214
|
}
|
|
207
215
|
function createExpectedRequiredArgumentError(name) {
|
|
208
216
|
return new icore_error_1.IcoreError('EXPECTED_REQUIRED_ARGUMENT', `Expected required argument '--${name}'`, {
|
|
217
|
+
reason: 'option',
|
|
209
218
|
argument: `--${name}`,
|
|
210
219
|
option: name
|
|
211
220
|
});
|
package/dist/options/schema.js
CHANGED
|
@@ -19,5 +19,6 @@ exports.mergeOptionsSchema = mergeOptionsSchema;
|
|
|
19
19
|
* Later schemas override earlier schemas with the same option name.
|
|
20
20
|
*/
|
|
21
21
|
function mergeOptionsSchema(schema, ...schemas) {
|
|
22
|
-
|
|
22
|
+
const merged = Object.assign(Object.create(null), schema, ...schemas);
|
|
23
|
+
return { ...merged };
|
|
23
24
|
}
|