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
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Allowed here:
|
|
6
6
|
* - the minimal text writer contract;
|
|
7
7
|
* - promise-returning sink support;
|
|
8
|
-
* - Node-style
|
|
8
|
+
* - Node-style backpressure and writable lifecycle handling;
|
|
9
9
|
*
|
|
10
10
|
* This file must not contain stdout/stderr selection or command semantics.
|
|
11
11
|
*/
|
|
@@ -24,6 +24,7 @@ export type BackpressureTextSink = {
|
|
|
24
24
|
* Creates a text writer that preserves writable-stream backpressure.
|
|
25
25
|
*
|
|
26
26
|
* Promise-returning sinks are awaited, and Node-style sinks returning `false`
|
|
27
|
-
* are resumed after their next `drain` event.
|
|
27
|
+
* are resumed after their next `drain` event. EventEmitter-compatible sinks
|
|
28
|
+
* reject when they emit `error` or `close` before draining.
|
|
28
29
|
*/
|
|
29
30
|
export declare function createBackpressureTextWriter(sink: BackpressureTextSink): TextWriter;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Allowed here:
|
|
7
7
|
* - the minimal text writer contract;
|
|
8
8
|
* - promise-returning sink support;
|
|
9
|
-
* - Node-style
|
|
9
|
+
* - Node-style backpressure and writable lifecycle handling;
|
|
10
10
|
*
|
|
11
11
|
* This file must not contain stdout/stderr selection or command semantics.
|
|
12
12
|
*/
|
|
@@ -16,7 +16,8 @@ exports.createBackpressureTextWriter = createBackpressureTextWriter;
|
|
|
16
16
|
* Creates a text writer that preserves writable-stream backpressure.
|
|
17
17
|
*
|
|
18
18
|
* Promise-returning sinks are awaited, and Node-style sinks returning `false`
|
|
19
|
-
* are resumed after their next `drain` event.
|
|
19
|
+
* are resumed after their next `drain` event. EventEmitter-compatible sinks
|
|
20
|
+
* reject when they emit `error` or `close` before draining.
|
|
20
21
|
*/
|
|
21
22
|
function createBackpressureTextWriter(sink) {
|
|
22
23
|
return {
|
|
@@ -32,6 +33,10 @@ function createBackpressureTextWriter(sink) {
|
|
|
32
33
|
if (typeof sink.once !== 'function') {
|
|
33
34
|
return;
|
|
34
35
|
}
|
|
36
|
+
if (isEventedBackpressureTextSink(sink)) {
|
|
37
|
+
await waitForEventedDrain(sink);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
35
40
|
await waitForDrain(sink.once.bind(sink));
|
|
36
41
|
}
|
|
37
42
|
};
|
|
@@ -47,3 +52,38 @@ function waitForDrain(onceDrain) {
|
|
|
47
52
|
onceDrain('drain', resolve);
|
|
48
53
|
});
|
|
49
54
|
}
|
|
55
|
+
function waitForEventedDrain(sink) {
|
|
56
|
+
return new Promise((resolve, reject) => {
|
|
57
|
+
let settled = false;
|
|
58
|
+
function cleanup() {
|
|
59
|
+
sink.off('drain', handleDrain);
|
|
60
|
+
sink.off('error', handleError);
|
|
61
|
+
sink.off('close', handleClose);
|
|
62
|
+
}
|
|
63
|
+
function settle(action) {
|
|
64
|
+
if (settled) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
settled = true;
|
|
68
|
+
cleanup();
|
|
69
|
+
action();
|
|
70
|
+
}
|
|
71
|
+
function handleDrain() {
|
|
72
|
+
settle(resolve);
|
|
73
|
+
}
|
|
74
|
+
function handleError(error) {
|
|
75
|
+
settle(() => reject(error));
|
|
76
|
+
}
|
|
77
|
+
function handleClose() {
|
|
78
|
+
settle(() => reject(new Error('Writable sink closed before drain')));
|
|
79
|
+
}
|
|
80
|
+
sink.once('error', handleError);
|
|
81
|
+
sink.once('close', handleClose);
|
|
82
|
+
sink.once('drain', handleDrain);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
function isEventedBackpressureTextSink(sink) {
|
|
86
|
+
const candidate = sink;
|
|
87
|
+
return (typeof sink.once === 'function'
|
|
88
|
+
&& typeof candidate.off === 'function');
|
|
89
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Allowed here:
|
|
6
6
|
* - CSV row joining;
|
|
7
|
-
* - CSV cell escaping for comma, quote, and newline values;
|
|
7
|
+
* - CSV cell escaping for comma, quote, carriage return, and newline values;
|
|
8
8
|
*
|
|
9
9
|
* This file must not contain application report mapping.
|
|
10
10
|
*/
|
|
@@ -28,7 +28,7 @@ function renderCsv(rows) {
|
|
|
28
28
|
}
|
|
29
29
|
function renderCsvValue(value) {
|
|
30
30
|
const text = String(value);
|
|
31
|
-
if (!/[",\n]/.test(text)) {
|
|
31
|
+
if (!/[",\r\n]/.test(text)) {
|
|
32
32
|
return text;
|
|
33
33
|
}
|
|
34
34
|
return `"${text.replaceAll('"', '""')}"`;
|
|
@@ -12,7 +12,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
12
12
|
exports.renderJson = renderJson;
|
|
13
13
|
/**
|
|
14
14
|
* Renders an already prepared value as pretty JSON with a trailing newline.
|
|
15
|
+
*
|
|
16
|
+
* Throws when the value has no top-level JSON representation.
|
|
15
17
|
*/
|
|
16
18
|
function renderJson(value) {
|
|
17
|
-
|
|
19
|
+
const json = JSON.stringify(value, null, 2);
|
|
20
|
+
if (json === undefined) {
|
|
21
|
+
throw new TypeError('Expected a JSON-serializable value');
|
|
22
|
+
}
|
|
23
|
+
return `${json}\n`;
|
|
18
24
|
}
|
|
@@ -70,10 +70,13 @@ function isPresentationResult(value) {
|
|
|
70
70
|
return result['value'] === null || isRecord(result['value']);
|
|
71
71
|
}
|
|
72
72
|
if (result['type'] === 'records') {
|
|
73
|
-
return
|
|
73
|
+
return isArrayOf(result['value'], isRecord);
|
|
74
74
|
}
|
|
75
|
-
if (result['type'] === 'table'
|
|
76
|
-
return
|
|
75
|
+
if (result['type'] === 'table') {
|
|
76
|
+
return isArrayOf(result['rows'], isTextTableRow);
|
|
77
|
+
}
|
|
78
|
+
if (result['type'] === 'csv') {
|
|
79
|
+
return isArrayOf(result['rows'], isCsvRow);
|
|
77
80
|
}
|
|
78
81
|
return false;
|
|
79
82
|
}
|
|
@@ -182,3 +185,25 @@ function assertNever(value) {
|
|
|
182
185
|
function isRecord(value) {
|
|
183
186
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
184
187
|
}
|
|
188
|
+
function isTextTableRow(value) {
|
|
189
|
+
return isArrayOf(value, (cell) => typeof cell === 'string');
|
|
190
|
+
}
|
|
191
|
+
function isCsvRow(value) {
|
|
192
|
+
return isArrayOf(value, isCsvCell);
|
|
193
|
+
}
|
|
194
|
+
function isCsvCell(value) {
|
|
195
|
+
return (typeof value === 'string'
|
|
196
|
+
|| typeof value === 'number'
|
|
197
|
+
|| typeof value === 'boolean');
|
|
198
|
+
}
|
|
199
|
+
function isArrayOf(value, isValue) {
|
|
200
|
+
if (!Array.isArray(value)) {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
for (const element of value) {
|
|
204
|
+
if (!isValue(element)) {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return true;
|
|
209
|
+
}
|
package/dist/terminal/app.d.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* - preparing and running command facades;
|
|
6
6
|
* - rendering presentation results;
|
|
7
7
|
* - writing command output through the output facade;
|
|
8
|
+
* - reporting terminal errors through caller-configured policy;
|
|
8
9
|
*
|
|
9
10
|
* This file must not contain argv tokenization, option validation internals,
|
|
10
11
|
* domain behavior, or application-specific report mapping.
|
|
@@ -21,6 +22,32 @@ import { type Output } from '../output/facade';
|
|
|
21
22
|
* no output. Other result shapes are rejected before writing to stdout.
|
|
22
23
|
*/
|
|
23
24
|
export type TerminalCommandOutput = string | AsyncIterable<string> | PresentationResult | undefined;
|
|
25
|
+
/** Terminal operation in which an error was observed. */
|
|
26
|
+
export type TerminalErrorPhase = 'prepare' | 'execute' | 'render' | 'write' | 'external';
|
|
27
|
+
/**
|
|
28
|
+
* Context supplied to terminal error policy callbacks.
|
|
29
|
+
*
|
|
30
|
+
* Prepared command data is guaranteed for command execution and terminal
|
|
31
|
+
* output phases. Errors outside terminal app operations may provide either
|
|
32
|
+
* arguments, prepared command data, or neither.
|
|
33
|
+
*/
|
|
34
|
+
export type TerminalErrorContext<TPrepared> = {
|
|
35
|
+
phase: 'prepare';
|
|
36
|
+
args: readonly string[];
|
|
37
|
+
} | {
|
|
38
|
+
phase: 'execute' | 'render' | 'write';
|
|
39
|
+
prepared: TPrepared;
|
|
40
|
+
args?: readonly string[];
|
|
41
|
+
} | {
|
|
42
|
+
phase: 'external';
|
|
43
|
+
args?: readonly string[];
|
|
44
|
+
prepared?: TPrepared;
|
|
45
|
+
};
|
|
46
|
+
/** Caller-owned terminal error rendering and process exit-code policy. */
|
|
47
|
+
export type TerminalErrorPolicy<TPrepared> = {
|
|
48
|
+
renderError?(error: unknown, context: TerminalErrorContext<TPrepared>): string;
|
|
49
|
+
resolveExitCode?(error: unknown, context: TerminalErrorContext<TPrepared>): number;
|
|
50
|
+
};
|
|
24
51
|
type BivariantCallback<TInput, TOutput> = {
|
|
25
52
|
bivarianceHack(input: TInput): TOutput;
|
|
26
53
|
}['bivarianceHack'];
|
|
@@ -47,6 +74,8 @@ export type TerminalAppOptions<TCommands extends readonly TerminalCommandDefinit
|
|
|
47
74
|
presentation?: Presentation;
|
|
48
75
|
/** Custom output facade. */
|
|
49
76
|
output?: Output;
|
|
77
|
+
/** Custom terminal error rendering and exit-code policy. */
|
|
78
|
+
errorPolicy?: TerminalErrorPolicy<PreparedCommand<TCommands[number]>>;
|
|
50
79
|
/** Resolves output format. */
|
|
51
80
|
resolveFormat?(prepared: PreparedCommand<TCommands[number]>): PresentationFormat | undefined;
|
|
52
81
|
};
|
|
@@ -54,6 +83,8 @@ export type TerminalApp<TCommands extends readonly TerminalCommandDefinition[]>
|
|
|
54
83
|
commands: Commands<TCommands>;
|
|
55
84
|
presentation: Presentation;
|
|
56
85
|
output: Output;
|
|
86
|
+
/** Renders an error to stderr and returns its process-style exit code. */
|
|
87
|
+
reportError(error: unknown, context?: TerminalErrorContext<PreparedCommand<TCommands[number]>>): Promise<number>;
|
|
57
88
|
/** No application context required. */
|
|
58
89
|
prepare(args: readonly string[], options?: CommandResolutionOptions): Promise<PreparedCommand<TCommands[number]>>;
|
|
59
90
|
/** Runs an already prepared command through terminal rendering and output. */
|
package/dist/terminal/app.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* - preparing and running command facades;
|
|
7
7
|
* - rendering presentation results;
|
|
8
8
|
* - writing command output through the output facade;
|
|
9
|
+
* - reporting terminal errors through caller-configured policy;
|
|
9
10
|
*
|
|
10
11
|
* This file must not contain argv tokenization, option validation internals,
|
|
11
12
|
* domain behavior, or application-specific report mapping.
|
|
@@ -27,68 +28,113 @@ function createTerminalApp(options) {
|
|
|
27
28
|
const presentation = options.presentation ?? (0, facade_1.createPresentation)();
|
|
28
29
|
const output = options.output ?? (0, facade_2.createOutput)();
|
|
29
30
|
const resolveFormat = options.resolveFormat ?? resolvePreparedFormat;
|
|
31
|
+
async function reportError(error, context = {
|
|
32
|
+
phase: 'external'
|
|
33
|
+
}) {
|
|
34
|
+
const renderedError = options.errorPolicy?.renderError === undefined
|
|
35
|
+
? renderTerminalError(error)
|
|
36
|
+
: options.errorPolicy.renderError(error, context);
|
|
37
|
+
await output.error(renderedError);
|
|
38
|
+
return options.errorPolicy?.resolveExitCode === undefined
|
|
39
|
+
? resolveTerminalExitCode()
|
|
40
|
+
: options.errorPolicy.resolveExitCode(error, context);
|
|
41
|
+
}
|
|
30
42
|
async function writePreparedOutput(prepared, terminalOutput) {
|
|
31
|
-
const
|
|
32
|
-
await writeTerminalOutput(
|
|
43
|
+
const renderedOutput = renderTerminalOutput(terminalOutput, resolveFormat(prepared), presentation);
|
|
44
|
+
await writeTerminalOutput(renderedOutput, output);
|
|
33
45
|
}
|
|
34
46
|
async function runPrepared(prepared, context) {
|
|
47
|
+
return runPreparedFromArgs(prepared, context);
|
|
48
|
+
}
|
|
49
|
+
async function runPreparedFromArgs(prepared, context, args) {
|
|
50
|
+
let result;
|
|
35
51
|
try {
|
|
36
|
-
|
|
37
|
-
|
|
52
|
+
result = await options.commands.run(prepared, context);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
return reportError(error, createPreparedErrorContext('execute', prepared, args));
|
|
56
|
+
}
|
|
57
|
+
let renderedOutput;
|
|
58
|
+
try {
|
|
59
|
+
renderedOutput = renderTerminalOutput(result, resolveFormat(prepared), presentation);
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
return reportError(error, createPreparedErrorContext('render', prepared, args));
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
await writeTerminalOutput(renderedOutput, output);
|
|
38
66
|
return 0;
|
|
39
67
|
}
|
|
40
68
|
catch (error) {
|
|
41
|
-
|
|
42
|
-
return 1;
|
|
69
|
+
return reportError(error, createPreparedErrorContext('write', prepared, args));
|
|
43
70
|
}
|
|
44
71
|
}
|
|
45
72
|
return {
|
|
46
73
|
commands: options.commands,
|
|
47
74
|
presentation,
|
|
48
75
|
output,
|
|
76
|
+
reportError,
|
|
49
77
|
prepare(args, commandOptions) {
|
|
50
78
|
return options.commands.prepare(args, commandOptions);
|
|
51
79
|
},
|
|
52
80
|
runPrepared,
|
|
53
81
|
writePreparedOutput,
|
|
54
82
|
async run(args, context, commandOptions) {
|
|
83
|
+
let prepared;
|
|
55
84
|
try {
|
|
56
|
-
|
|
57
|
-
return runPrepared(prepared, context);
|
|
85
|
+
prepared = await options.commands.prepare(args, commandOptions);
|
|
58
86
|
}
|
|
59
87
|
catch (error) {
|
|
60
|
-
|
|
61
|
-
|
|
88
|
+
return reportError(error, {
|
|
89
|
+
phase: 'prepare',
|
|
90
|
+
args
|
|
91
|
+
});
|
|
62
92
|
}
|
|
93
|
+
return runPreparedFromArgs(prepared, context, args);
|
|
63
94
|
}
|
|
64
95
|
};
|
|
65
96
|
}
|
|
66
|
-
|
|
67
|
-
* Writes only terminal-supported command output shapes.
|
|
68
|
-
*
|
|
69
|
-
* This keeps the terminal boundary explicit: application objects must be
|
|
70
|
-
* mapped to text or presentation views before they reach stdout.
|
|
71
|
-
*/
|
|
72
|
-
async function writeTerminalOutput(result, format, presentation, output) {
|
|
97
|
+
function renderTerminalOutput(result, format, presentation) {
|
|
73
98
|
if (result === undefined) {
|
|
74
|
-
return;
|
|
99
|
+
return undefined;
|
|
75
100
|
}
|
|
76
101
|
if (typeof result === 'string') {
|
|
77
|
-
|
|
78
|
-
return;
|
|
102
|
+
return result;
|
|
79
103
|
}
|
|
80
104
|
if (isAsyncIterable(result)) {
|
|
81
|
-
|
|
82
|
-
await output.write(chunk);
|
|
83
|
-
}
|
|
84
|
-
return;
|
|
105
|
+
return result;
|
|
85
106
|
}
|
|
86
107
|
if ((0, result_renderer_1.isPresentationResult)(result)) {
|
|
87
|
-
|
|
88
|
-
return;
|
|
108
|
+
return presentation.render(result, format);
|
|
89
109
|
}
|
|
90
110
|
throw new Error('Expected terminal command output');
|
|
91
111
|
}
|
|
112
|
+
/** Writes already rendered terminal text while preserving stream backpressure. */
|
|
113
|
+
async function writeTerminalOutput(result, output) {
|
|
114
|
+
if (result === undefined) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (typeof result === 'string') {
|
|
118
|
+
await output.write(result);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
for await (const chunk of result) {
|
|
122
|
+
await output.write(chunk);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function createPreparedErrorContext(phase, prepared, args) {
|
|
126
|
+
if (args === undefined) {
|
|
127
|
+
return {
|
|
128
|
+
phase,
|
|
129
|
+
prepared
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
phase,
|
|
134
|
+
prepared,
|
|
135
|
+
args
|
|
136
|
+
};
|
|
137
|
+
}
|
|
92
138
|
/**
|
|
93
139
|
* Keeps the default format convention local to terminal composition.
|
|
94
140
|
*/
|
|
@@ -107,6 +153,9 @@ function renderTerminalError(error) {
|
|
|
107
153
|
}
|
|
108
154
|
return `${String(error)}\n`;
|
|
109
155
|
}
|
|
156
|
+
function resolveTerminalExitCode() {
|
|
157
|
+
return 1;
|
|
158
|
+
}
|
|
110
159
|
function isAsyncIterable(value) {
|
|
111
160
|
return (typeof value === 'object'
|
|
112
161
|
&& value !== null
|
package/examples/terminal-app.md
CHANGED
|
@@ -116,3 +116,70 @@ const appWithFormatPolicy = createTerminalApp({
|
|
|
116
116
|
This is useful when one command has a different default output contract. It is
|
|
117
117
|
also a point where the application can make a bad abstraction: avoid hiding
|
|
118
118
|
format decisions here when a normal `--format` option would be clearer.
|
|
119
|
+
|
|
120
|
+
## Reuse Error Reporting In A Custom Lifecycle
|
|
121
|
+
|
|
122
|
+
Configure one error policy when the application sometimes uses `app.run(...)`
|
|
123
|
+
and sometimes owns command execution itself. The built-in flow and explicit
|
|
124
|
+
`app.reportError(...)` calls use the same policy.
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
import { isIcoreError } from 'icore';
|
|
128
|
+
|
|
129
|
+
const appWithErrorPolicy = createTerminalApp({
|
|
130
|
+
commands,
|
|
131
|
+
presentation,
|
|
132
|
+
output: createOutput(),
|
|
133
|
+
errorPolicy: {
|
|
134
|
+
renderError(error) {
|
|
135
|
+
const message = error instanceof Error
|
|
136
|
+
? error.message
|
|
137
|
+
: String(error);
|
|
138
|
+
|
|
139
|
+
return `${message}\n`;
|
|
140
|
+
},
|
|
141
|
+
resolveExitCode(error) {
|
|
142
|
+
return isIcoreError(error) && error.category === 'usage'
|
|
143
|
+
? 2
|
|
144
|
+
: 1;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
When the application owns context creation and cleanup, it can prepare and run
|
|
151
|
+
the command explicitly while keeping terminal diagnostics consistent:
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
const prepared = await appWithErrorPolicy.prepare(args, {
|
|
155
|
+
strict: true
|
|
156
|
+
});
|
|
157
|
+
let result;
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
result = await appWithErrorPolicy.commands.run(prepared, context);
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
process.exitCode = await appWithErrorPolicy.reportError(error, {
|
|
164
|
+
phase: 'execute',
|
|
165
|
+
args,
|
|
166
|
+
prepared
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
await appWithErrorPolicy.writePreparedOutput(prepared, result);
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
process.exitCode = await appWithErrorPolicy.reportError(error, {
|
|
177
|
+
phase: 'write',
|
|
178
|
+
args,
|
|
179
|
+
prepared
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
The application still owns help text and lifecycle behavior. The policy only
|
|
185
|
+
provides a shared terminal reporting boundary.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "icore",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Declarative command line interface and terminal presentation mechanics for Node.js",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"command-line",
|
|
@@ -33,8 +33,9 @@
|
|
|
33
33
|
"types": "dist/index.d.ts",
|
|
34
34
|
"scripts": {
|
|
35
35
|
"build": "tsc",
|
|
36
|
+
"prepack": "tsc",
|
|
36
37
|
"test": "fwa --prune",
|
|
37
|
-
"lint": "eslint
|
|
38
|
+
"lint": "eslint"
|
|
38
39
|
},
|
|
39
40
|
"devDependencies": {
|
|
40
41
|
"@eslint/js": "^9.39.4",
|