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.
Files changed (50) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/{argv.d.ts → argv/parser.d.ts} +3 -1
  3. package/dist/{argv.js → argv/parser.js} +5 -5
  4. package/dist/{commands.d.ts → command/mechanics.d.ts} +79 -6
  5. package/dist/{commands.js → command/mechanics.js} +60 -15
  6. package/dist/{errors.d.ts → errors/icore-error.d.ts} +2 -0
  7. package/dist/{errors.js → errors/icore-error.js} +2 -0
  8. package/dist/index.d.ts +28 -1
  9. package/dist/index.js +61 -16
  10. package/dist/options/parser.d.ts +43 -0
  11. package/dist/{options.js → options/parser.js} +27 -17
  12. package/dist/{options.d.ts → options/schema.d.ts} +16 -23
  13. package/dist/options/schema.js +23 -0
  14. package/dist/output/facade.d.ts +28 -0
  15. package/dist/output/facade.js +32 -0
  16. package/dist/output/node-writer.d.ts +17 -0
  17. package/dist/output/node-writer.js +25 -0
  18. package/dist/output/text-writer.d.ts +29 -0
  19. package/dist/output/text-writer.js +49 -0
  20. package/dist/presentation/facade.d.ts +39 -0
  21. package/dist/presentation/facade.js +47 -0
  22. package/dist/presentation/format-options.d.ts +31 -0
  23. package/dist/presentation/format-options.js +37 -0
  24. package/dist/presentation/renderers/csv.d.ts +18 -0
  25. package/dist/presentation/renderers/csv.js +35 -0
  26. package/dist/presentation/renderers/json.d.ts +13 -0
  27. package/dist/presentation/renderers/json.js +18 -0
  28. package/dist/presentation/renderers/table.d.ts +14 -0
  29. package/dist/presentation/renderers/table.js +29 -0
  30. package/dist/presentation/result-renderer.d.ts +25 -0
  31. package/dist/presentation/result-renderer.js +184 -0
  32. package/dist/presentation/view.d.ts +58 -0
  33. package/dist/presentation/view.js +56 -0
  34. package/dist/terminal/app.d.ts +51 -0
  35. package/dist/terminal/app.js +100 -0
  36. package/examples/cli-argument-syntax.md +218 -0
  37. package/examples/command-resolution.md +174 -0
  38. package/examples/custom-command-flow.md +109 -0
  39. package/examples/option-schemas.md +206 -0
  40. package/examples/output-writers.md +128 -0
  41. package/examples/practical-cli-patterns.md +385 -0
  42. package/examples/presentation-output.md +66 -0
  43. package/examples/presentation-primitives.md +190 -0
  44. package/examples/readme.md +48 -0
  45. package/examples/terminal-app.md +118 -0
  46. package/examples/two-phase-primitives.md +282 -0
  47. package/package.json +9 -3
  48. package/readme.md +281 -290
  49. package/dist/cli.d.ts +0 -14
  50. package/dist/cli.js +0 -31
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ /**
3
+ * The text table renderer module converts text rows to aligned terminal text.
4
+ *
5
+ * Allowed here:
6
+ * - computing column widths;
7
+ * - rendering already prepared string cells;
8
+ *
9
+ * This file must not contain domain-specific scalar formatting.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.renderTextTable = renderTextTable;
13
+ /**
14
+ * Renders prepared text rows as an aligned table without changing cell values.
15
+ */
16
+ function renderTextTable(rows) {
17
+ if (rows.length === 0) {
18
+ return '';
19
+ }
20
+ const columnCount = Math.max(...rows.map((row) => row.length));
21
+ const widths = Array.from({ length: columnCount }, (_, columnIndex) => Math.max(...rows.map((row) => (row[columnIndex] ?? '').length)));
22
+ return [
23
+ ...rows.map((row) => widths
24
+ .map((width, columnIndex) => (row[columnIndex] ?? '').padEnd(width))
25
+ .join(' ')
26
+ .trimEnd()),
27
+ ''
28
+ ].join('\n');
29
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The presentation result renderer converts generic presentation views into
3
+ * terminal text using the selected output format.
4
+ *
5
+ * Allowed here:
6
+ * - dispatching presentation views to JSON, CSV, and table renderers;
7
+ * - converting generic records to renderer input rows;
8
+ * - validating generic presentation result shapes;
9
+ *
10
+ * This file must not contain command execution, stdout/stderr delivery, or
11
+ * application report mapping.
12
+ */
13
+ import type { PresentationFormat } from './format-options';
14
+ import type { PresentationResult } from './view';
15
+ /**
16
+ * Renders a presentation result after the caller selects the terminal format.
17
+ *
18
+ * Record-like views are projected to table or CSV rows inside this boundary so
19
+ * applications do not duplicate generic renderer input shaping.
20
+ */
21
+ export declare function renderPresentationResult(result: PresentationResult, format?: PresentationFormat): string;
22
+ /**
23
+ * Checks whether an unknown command result is a supported presentation result.
24
+ */
25
+ export declare function isPresentationResult(value: unknown): value is PresentationResult;
@@ -0,0 +1,184 @@
1
+ "use strict";
2
+ /**
3
+ * The presentation result renderer converts generic presentation views into
4
+ * terminal text using the selected output format.
5
+ *
6
+ * Allowed here:
7
+ * - dispatching presentation views to JSON, CSV, and table renderers;
8
+ * - converting generic records to renderer input rows;
9
+ * - validating generic presentation result shapes;
10
+ *
11
+ * This file must not contain command execution, stdout/stderr delivery, or
12
+ * application report mapping.
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.renderPresentationResult = renderPresentationResult;
16
+ exports.isPresentationResult = isPresentationResult;
17
+ const csv_1 = require("./renderers/csv");
18
+ const json_1 = require("./renderers/json");
19
+ const table_1 = require("./renderers/table");
20
+ /**
21
+ * Renders a presentation result after the caller selects the terminal format.
22
+ *
23
+ * Record-like views are projected to table or CSV rows inside this boundary so
24
+ * applications do not duplicate generic renderer input shaping.
25
+ */
26
+ function renderPresentationResult(result, format = 'table') {
27
+ if (result.type === 'empty') {
28
+ return '';
29
+ }
30
+ if (result.type === 'text') {
31
+ return result.value;
32
+ }
33
+ if (format === 'json') {
34
+ return (0, json_1.renderJson)(presentationResultToJsonValue(result));
35
+ }
36
+ if (result.type === 'table') {
37
+ return format === 'csv'
38
+ ? (0, csv_1.renderCsv)(result.rows)
39
+ : (0, table_1.renderTextTable)(result.rows);
40
+ }
41
+ if (result.type === 'csv') {
42
+ return format === 'csv'
43
+ ? (0, csv_1.renderCsv)(result.rows)
44
+ : (0, table_1.renderTextTable)(rowsToTextRows(result.rows));
45
+ }
46
+ if (result.type === 'record') {
47
+ return format === 'csv'
48
+ ? (0, csv_1.renderCsv)(recordToCsvRows(result.value))
49
+ : (0, table_1.renderTextTable)(recordToTextRows(result.value));
50
+ }
51
+ return format === 'csv'
52
+ ? (0, csv_1.renderCsv)(recordsToCsvRows(result.value))
53
+ : (0, table_1.renderTextTable)(recordsToTextRows(result.value));
54
+ }
55
+ /**
56
+ * Checks whether an unknown command result is a supported presentation result.
57
+ */
58
+ function isPresentationResult(value) {
59
+ if (typeof value !== 'object' || value === null || !('type' in value)) {
60
+ return false;
61
+ }
62
+ const result = value;
63
+ if (result['type'] === 'empty') {
64
+ return true;
65
+ }
66
+ if (result['type'] === 'text') {
67
+ return typeof result['value'] === 'string';
68
+ }
69
+ if (result['type'] === 'record') {
70
+ return result['value'] === null || isRecord(result['value']);
71
+ }
72
+ if (result['type'] === 'records') {
73
+ return Array.isArray(result['value']);
74
+ }
75
+ if (result['type'] === 'table' || result['type'] === 'csv') {
76
+ return Array.isArray(result['rows']);
77
+ }
78
+ return false;
79
+ }
80
+ function presentationResultToJsonValue(result) {
81
+ if (result.type === 'empty') {
82
+ return null;
83
+ }
84
+ if (result.type === 'text') {
85
+ return result.value;
86
+ }
87
+ if (result.type === 'record') {
88
+ return result.value;
89
+ }
90
+ if (result.type === 'records') {
91
+ return result.value;
92
+ }
93
+ if (result.type === 'table') {
94
+ return result.rows;
95
+ }
96
+ if (result.type === 'csv') {
97
+ return result.rows;
98
+ }
99
+ return assertNever(result);
100
+ }
101
+ function recordToTextRows(record) {
102
+ if (record === null) {
103
+ return [];
104
+ }
105
+ return [
106
+ ['field', 'value'],
107
+ ...Object.keys(record).map((key) => [
108
+ key,
109
+ formatPresentationCell(record[key])
110
+ ])
111
+ ];
112
+ }
113
+ function recordsToTextRows(records) {
114
+ if (records.length === 0) {
115
+ return [];
116
+ }
117
+ const keys = collectRecordKeys(records);
118
+ return [
119
+ keys,
120
+ ...records.map((record) => keys.map((key) => formatPresentationCell(record[key])))
121
+ ];
122
+ }
123
+ function recordToCsvRows(record) {
124
+ if (record === null) {
125
+ return [];
126
+ }
127
+ const keys = Object.keys(record);
128
+ return [
129
+ keys,
130
+ keys.map((key) => toCsvCell(record[key]))
131
+ ];
132
+ }
133
+ function recordsToCsvRows(records) {
134
+ if (records.length === 0) {
135
+ return [];
136
+ }
137
+ const keys = collectRecordKeys(records);
138
+ return [
139
+ keys,
140
+ ...records.map((record) => keys.map((key) => toCsvCell(record[key])))
141
+ ];
142
+ }
143
+ function rowsToTextRows(rows) {
144
+ return rows.map((row) => row.map(formatPresentationCell));
145
+ }
146
+ function collectRecordKeys(records) {
147
+ const keys = [];
148
+ const seen = new Set();
149
+ for (const record of records) {
150
+ for (const key of Object.keys(record)) {
151
+ if (!seen.has(key)) {
152
+ keys.push(key);
153
+ seen.add(key);
154
+ }
155
+ }
156
+ }
157
+ return keys;
158
+ }
159
+ function formatPresentationCell(value) {
160
+ if (value === null || value === undefined) {
161
+ return '';
162
+ }
163
+ if (typeof value === 'string') {
164
+ return value;
165
+ }
166
+ if (typeof value === 'number' || typeof value === 'boolean') {
167
+ return String(value);
168
+ }
169
+ return JSON.stringify(value) ?? String(value);
170
+ }
171
+ function toCsvCell(value) {
172
+ if (typeof value === 'string'
173
+ || typeof value === 'number'
174
+ || typeof value === 'boolean') {
175
+ return value;
176
+ }
177
+ return formatPresentationCell(value);
178
+ }
179
+ function assertNever(value) {
180
+ throw new Error(`Unexpected presentation result: ${String(value)}`);
181
+ }
182
+ function isRecord(value) {
183
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
184
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * The presentation view module defines generic terminal view models.
3
+ *
4
+ * Allowed here:
5
+ * - JSON-like record views;
6
+ * - explicit table and CSV views;
7
+ * - text and empty terminal result views;
8
+ *
9
+ * This file must not contain renderer implementation or application report
10
+ * mapping.
11
+ */
12
+ export type CsvCell = string | number | boolean;
13
+ export type CsvRow = readonly CsvCell[];
14
+ export type TextTableRow = readonly string[];
15
+ export type PresentationRecord = Readonly<Record<string, unknown>>;
16
+ export type EmptyPresentationView = {
17
+ type: 'empty';
18
+ };
19
+ export type TextPresentationView = {
20
+ type: 'text';
21
+ value: string;
22
+ };
23
+ export type RecordPresentationView = {
24
+ type: 'record';
25
+ value: PresentationRecord | null;
26
+ };
27
+ export type RecordsPresentationView = {
28
+ type: 'records';
29
+ value: readonly PresentationRecord[];
30
+ };
31
+ export type TablePresentationView = {
32
+ type: 'table';
33
+ rows: readonly TextTableRow[];
34
+ };
35
+ export type CsvPresentationView = {
36
+ type: 'csv';
37
+ rows: readonly CsvRow[];
38
+ };
39
+ export type PresentationView = EmptyPresentationView | TextPresentationView | RecordPresentationView | RecordsPresentationView | TablePresentationView | CsvPresentationView;
40
+ export type PresentationResult = PresentationView;
41
+ export type PresentationViewFactory = {
42
+ /** No output. */
43
+ empty(): EmptyPresentationView;
44
+ /** Ready terminal text. */
45
+ text(value: string): TextPresentationView;
46
+ /** One generic record. */
47
+ record(value: PresentationRecord | null): RecordPresentationView;
48
+ /** Generic records. */
49
+ records(value: readonly PresentationRecord[]): RecordsPresentationView;
50
+ /** Prepared text rows. */
51
+ table(rows: readonly TextTableRow[]): TablePresentationView;
52
+ /** CSV scalar rows. */
53
+ csv(rows: readonly CsvRow[]): CsvPresentationView;
54
+ };
55
+ /**
56
+ * Creates view-model factory methods without binding them to an output format.
57
+ */
58
+ export declare function createPresentationViewFactory(): PresentationViewFactory;
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ /**
3
+ * The presentation view module defines generic terminal view models.
4
+ *
5
+ * Allowed here:
6
+ * - JSON-like record views;
7
+ * - explicit table and CSV views;
8
+ * - text and empty terminal result views;
9
+ *
10
+ * This file must not contain renderer implementation or application report
11
+ * mapping.
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.createPresentationViewFactory = createPresentationViewFactory;
15
+ /**
16
+ * Creates view-model factory methods without binding them to an output format.
17
+ */
18
+ function createPresentationViewFactory() {
19
+ return {
20
+ empty() {
21
+ return {
22
+ type: 'empty'
23
+ };
24
+ },
25
+ text(value) {
26
+ return {
27
+ type: 'text',
28
+ value
29
+ };
30
+ },
31
+ record(value) {
32
+ return {
33
+ type: 'record',
34
+ value
35
+ };
36
+ },
37
+ records(value) {
38
+ return {
39
+ type: 'records',
40
+ value
41
+ };
42
+ },
43
+ table(rows) {
44
+ return {
45
+ type: 'table',
46
+ rows
47
+ };
48
+ },
49
+ csv(rows) {
50
+ return {
51
+ type: 'csv',
52
+ rows
53
+ };
54
+ }
55
+ };
56
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * The terminal app module composes command, presentation, and output mechanics.
3
+ *
4
+ * Allowed here:
5
+ * - preparing and running command facades;
6
+ * - rendering presentation results;
7
+ * - writing command output through the output facade;
8
+ *
9
+ * This file must not contain argv tokenization, option validation internals,
10
+ * domain behavior, or application-specific report mapping.
11
+ */
12
+ import type { CommandDefinition, CommandResolutionOptions, Commands, PreparedCommand } from '../command/mechanics';
13
+ import type { OptionsSchema } from '../options/schema';
14
+ import { type Presentation, type PresentationResult } from '../presentation/facade';
15
+ import { type PresentationFormat } from '../presentation/format-options';
16
+ import { type Output } from '../output/facade';
17
+ /**
18
+ * Supported handler result shapes for the terminal app boundary.
19
+ *
20
+ * Domain commands may return ready text, streaming text, a presentation view,
21
+ * or no output. Other result shapes are rejected before writing to stdout.
22
+ */
23
+ export type TerminalCommandOutput = string | AsyncIterable<string> | PresentationResult | undefined;
24
+ type TerminalCommandDefinition<TContext> = CommandDefinition<OptionsSchema, TContext, TerminalCommandOutput, readonly [string, ...string[]], unknown, unknown>;
25
+ export type TerminalAppOptions<TContext, TCommands extends readonly TerminalCommandDefinition<TContext>[]> = {
26
+ commands: Commands<TCommands>;
27
+ /** Custom presentation facade. */
28
+ presentation?: Presentation;
29
+ /** Custom output facade. */
30
+ output?: Output;
31
+ /** Resolves output format. */
32
+ resolveFormat?(prepared: PreparedCommand<TCommands[number]>): PresentationFormat | undefined;
33
+ };
34
+ export type TerminalApp<TContext, TCommands extends readonly TerminalCommandDefinition<TContext>[]> = {
35
+ commands: Commands<TCommands>;
36
+ presentation: Presentation;
37
+ output: Output;
38
+ /** No application context required. */
39
+ prepare(args: readonly string[], options?: CommandResolutionOptions): Promise<PreparedCommand<TCommands[number]>>;
40
+ /** Returns a process-style exit code. */
41
+ run(args: readonly string[], context: TContext, options?: CommandResolutionOptions): Promise<number>;
42
+ };
43
+ /**
44
+ * Composes command mechanics, presentation rendering, and output delivery into
45
+ * a terminal application boundary.
46
+ *
47
+ * Command handlers keep ownership of application work; this facade only
48
+ * prepares commands, renders supported terminal results, and writes output.
49
+ */
50
+ export declare function createTerminalApp<TContext, const TCommands extends readonly TerminalCommandDefinition<TContext>[]>(options: TerminalAppOptions<TContext, TCommands>): TerminalApp<TContext, TCommands>;
51
+ export {};
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ /**
3
+ * The terminal app module composes command, presentation, and output mechanics.
4
+ *
5
+ * Allowed here:
6
+ * - preparing and running command facades;
7
+ * - rendering presentation results;
8
+ * - writing command output through the output facade;
9
+ *
10
+ * This file must not contain argv tokenization, option validation internals,
11
+ * domain behavior, or application-specific report mapping.
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.createTerminalApp = createTerminalApp;
15
+ const facade_1 = require("../presentation/facade");
16
+ const format_options_1 = require("../presentation/format-options");
17
+ const result_renderer_1 = require("../presentation/result-renderer");
18
+ const facade_2 = require("../output/facade");
19
+ /**
20
+ * Composes command mechanics, presentation rendering, and output delivery into
21
+ * a terminal application boundary.
22
+ *
23
+ * Command handlers keep ownership of application work; this facade only
24
+ * prepares commands, renders supported terminal results, and writes output.
25
+ */
26
+ function createTerminalApp(options) {
27
+ const presentation = options.presentation ?? (0, facade_1.createPresentation)();
28
+ const output = options.output ?? (0, facade_2.createOutput)();
29
+ const resolveFormat = options.resolveFormat ?? resolvePreparedFormat;
30
+ return {
31
+ commands: options.commands,
32
+ presentation,
33
+ output,
34
+ prepare(args, commandOptions) {
35
+ return options.commands.prepare(args, commandOptions);
36
+ },
37
+ async run(args, context, commandOptions) {
38
+ try {
39
+ const prepared = await options.commands.prepare(args, commandOptions);
40
+ const result = await options.commands.run(prepared, context);
41
+ const format = resolveFormat(prepared);
42
+ await writeTerminalOutput(result, format, presentation, output);
43
+ return 0;
44
+ }
45
+ catch (error) {
46
+ await output.error(renderTerminalError(error));
47
+ return 1;
48
+ }
49
+ }
50
+ };
51
+ }
52
+ /**
53
+ * Writes only terminal-supported command output shapes.
54
+ *
55
+ * This keeps the terminal boundary explicit: application objects must be
56
+ * mapped to text or presentation views before they reach stdout.
57
+ */
58
+ async function writeTerminalOutput(result, format, presentation, output) {
59
+ if (result === undefined) {
60
+ return;
61
+ }
62
+ if (typeof result === 'string') {
63
+ await output.write(result);
64
+ return;
65
+ }
66
+ if (isAsyncIterable(result)) {
67
+ for await (const chunk of result) {
68
+ await output.write(chunk);
69
+ }
70
+ return;
71
+ }
72
+ if ((0, result_renderer_1.isPresentationResult)(result)) {
73
+ await output.write(presentation.render(result, format));
74
+ return;
75
+ }
76
+ throw new Error('Expected terminal command output');
77
+ }
78
+ /**
79
+ * Keeps the default format convention local to terminal composition.
80
+ */
81
+ function resolvePreparedFormat(prepared) {
82
+ const options = prepared.options;
83
+ const format = options['format'];
84
+ return (0, format_options_1.isPresentationFormat)(format) ? format : undefined;
85
+ }
86
+ /**
87
+ * Converts thrown values to terminal error text without changing the error
88
+ * contract exposed to command handlers.
89
+ */
90
+ function renderTerminalError(error) {
91
+ if (error instanceof Error) {
92
+ return `${error.message}\n`;
93
+ }
94
+ return `${String(error)}\n`;
95
+ }
96
+ function isAsyncIterable(value) {
97
+ return (typeof value === 'object'
98
+ && value !== null
99
+ && Symbol.asyncIterator in value);
100
+ }