@portel/cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli-formatter.d.ts +65 -0
- package/dist/cli-formatter.d.ts.map +1 -0
- package/dist/cli-formatter.js +585 -0
- package/dist/cli-formatter.js.map +1 -0
- package/dist/context.d.ts +23 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +25 -0
- package/dist/context.js.map +1 -0
- package/dist/fuzzy-matcher.d.ts +39 -0
- package/dist/fuzzy-matcher.d.ts.map +1 -0
- package/dist/fuzzy-matcher.js +89 -0
- package/dist/fuzzy-matcher.js.map +1 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +42 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +59 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +147 -0
- package/dist/logger.js.map +1 -0
- package/dist/progress.d.ts +67 -0
- package/dist/progress.d.ts.map +1 -0
- package/dist/progress.js +165 -0
- package/dist/progress.js.map +1 -0
- package/dist/text-utils.d.ts +34 -0
- package/dist/text-utils.d.ts.map +1 -0
- package/dist/text-utils.js +104 -0
- package/dist/text-utils.js.map +1 -0
- package/dist/types.d.ts +59 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +50 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Output Formatter
|
|
3
|
+
*
|
|
4
|
+
* Shared formatting utilities for beautiful CLI output.
|
|
5
|
+
* Used by photon CLI, lumina, ncp, and other projects.
|
|
6
|
+
*
|
|
7
|
+
* Structural formats:
|
|
8
|
+
* - primitive: Single values (string, number, boolean)
|
|
9
|
+
* - table: Flat objects or arrays of flat objects (bordered tables)
|
|
10
|
+
* - tree: Nested/hierarchical structures (indented)
|
|
11
|
+
* - list: Arrays of primitives (bullet points)
|
|
12
|
+
* - none: No data to display
|
|
13
|
+
*
|
|
14
|
+
* Content formats:
|
|
15
|
+
* - json: Pretty-printed JSON
|
|
16
|
+
* - markdown: Rendered markdown
|
|
17
|
+
* - yaml: YAML content
|
|
18
|
+
* - code / code:<lang>: Syntax highlighted code
|
|
19
|
+
*/
|
|
20
|
+
import type { OutputFormat } from './types.js';
|
|
21
|
+
/**
|
|
22
|
+
* Format and output data with optional format hint
|
|
23
|
+
*/
|
|
24
|
+
export declare function formatOutput(data: any, hint?: OutputFormat): void;
|
|
25
|
+
/**
|
|
26
|
+
* Detect format type from data structure
|
|
27
|
+
*/
|
|
28
|
+
export declare function detectFormat(data: any): OutputFormat;
|
|
29
|
+
export declare function renderPrimitive(value: any): void;
|
|
30
|
+
export declare function renderList(data: any[]): void;
|
|
31
|
+
export declare function renderTable(data: any): void;
|
|
32
|
+
export declare function renderTree(data: any, indent?: string): void;
|
|
33
|
+
/**
|
|
34
|
+
* Render data as a bordered card
|
|
35
|
+
* Great for single objects with mixed content types
|
|
36
|
+
*/
|
|
37
|
+
export declare function renderCard(data: any): void;
|
|
38
|
+
/**
|
|
39
|
+
* Render data as tabbed sections
|
|
40
|
+
* Each top-level key becomes a tab
|
|
41
|
+
*/
|
|
42
|
+
export declare function renderTabs(data: any): void;
|
|
43
|
+
/**
|
|
44
|
+
* Render data as collapsible accordion sections
|
|
45
|
+
* Similar to tabs but with visual collapse indicators
|
|
46
|
+
*/
|
|
47
|
+
export declare function renderAccordion(data: any): void;
|
|
48
|
+
export declare function renderNone(): void;
|
|
49
|
+
export declare function formatKey(key: string): string;
|
|
50
|
+
export declare function formatValue(value: any): string | number | boolean;
|
|
51
|
+
export declare const STATUS: {
|
|
52
|
+
readonly OK: "ok";
|
|
53
|
+
readonly UPDATE: "update";
|
|
54
|
+
readonly WARN: "warn";
|
|
55
|
+
readonly ERROR: "!";
|
|
56
|
+
readonly OFF: "off";
|
|
57
|
+
readonly UNKNOWN: "?";
|
|
58
|
+
};
|
|
59
|
+
export declare function formatToMimeType(format: OutputFormat): string | undefined;
|
|
60
|
+
export declare function printSuccess(message: string): void;
|
|
61
|
+
export declare function printError(message: string): void;
|
|
62
|
+
export declare function printInfo(message: string): void;
|
|
63
|
+
export declare function printWarning(message: string): void;
|
|
64
|
+
export declare function printHeader(title: string): void;
|
|
65
|
+
//# sourceMappingURL=cli-formatter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-formatter.d.ts","sourceRoot":"","sources":["../src/cli-formatter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAI/C;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,YAAY,GAAG,IAAI,CASjE;AAyJD;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,GAAG,GAAG,YAAY,CAuBpD;AAUD,wBAAgB,eAAe,CAAC,KAAK,EAAE,GAAG,GAAG,IAAI,CAMhD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAgB5C;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,GAAG,GAAG,IAAI,CA2E3C;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,GAAE,MAAW,GAAG,IAAI,CAwC/D;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,GAAG,GAAG,IAAI,CAU1C;AA8ED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,GAAG,GAAG,IAAI,CAuC1C;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,GAAG,GAAG,IAAI,CAsD/C;AAED,wBAAgB,UAAU,IAAI,IAAI,CAEjC;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAO7C;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,GAAG,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAIjE;AAED,eAAO,MAAM,MAAM;;;;;;;CAOT,CAAC;AAEX,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,GAAG,SAAS,CAazE;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAElD;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAEhD;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE/C;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAElD;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAG/C"}
|
|
@@ -0,0 +1,585 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Output Formatter
|
|
3
|
+
*
|
|
4
|
+
* Shared formatting utilities for beautiful CLI output.
|
|
5
|
+
* Used by photon CLI, lumina, ncp, and other projects.
|
|
6
|
+
*
|
|
7
|
+
* Structural formats:
|
|
8
|
+
* - primitive: Single values (string, number, boolean)
|
|
9
|
+
* - table: Flat objects or arrays of flat objects (bordered tables)
|
|
10
|
+
* - tree: Nested/hierarchical structures (indented)
|
|
11
|
+
* - list: Arrays of primitives (bullet points)
|
|
12
|
+
* - none: No data to display
|
|
13
|
+
*
|
|
14
|
+
* Content formats:
|
|
15
|
+
* - json: Pretty-printed JSON
|
|
16
|
+
* - markdown: Rendered markdown
|
|
17
|
+
* - yaml: YAML content
|
|
18
|
+
* - code / code:<lang>: Syntax highlighted code
|
|
19
|
+
*/
|
|
20
|
+
import { highlight } from 'cli-highlight';
|
|
21
|
+
import chalk from 'chalk';
|
|
22
|
+
/**
|
|
23
|
+
* Format and output data with optional format hint
|
|
24
|
+
*/
|
|
25
|
+
export function formatOutput(data, hint) {
|
|
26
|
+
const format = hint || detectFormat(data);
|
|
27
|
+
if (typeof data === 'string' && isContentFormat(format)) {
|
|
28
|
+
renderContent(data, format);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
formatDataWithHint(data, format);
|
|
32
|
+
}
|
|
33
|
+
function isContentFormat(format) {
|
|
34
|
+
return ['json', 'markdown', 'yaml', 'xml', 'html'].includes(format) ||
|
|
35
|
+
format === 'code' ||
|
|
36
|
+
format.startsWith('code:');
|
|
37
|
+
}
|
|
38
|
+
function renderContent(content, format) {
|
|
39
|
+
switch (format) {
|
|
40
|
+
case 'json':
|
|
41
|
+
renderJson(content);
|
|
42
|
+
break;
|
|
43
|
+
case 'markdown':
|
|
44
|
+
renderMarkdown(content);
|
|
45
|
+
break;
|
|
46
|
+
case 'yaml':
|
|
47
|
+
renderYaml(content);
|
|
48
|
+
break;
|
|
49
|
+
case 'xml':
|
|
50
|
+
case 'html':
|
|
51
|
+
renderXml(content);
|
|
52
|
+
break;
|
|
53
|
+
default:
|
|
54
|
+
if (format === 'code' || format.startsWith('code:')) {
|
|
55
|
+
const lang = format === 'code' ? undefined : format.split(':')[1];
|
|
56
|
+
renderCode(content, lang);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
console.log(content);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function renderJson(content) {
|
|
64
|
+
try {
|
|
65
|
+
const parsed = typeof content === 'string' ? JSON.parse(content) : content;
|
|
66
|
+
const formatted = JSON.stringify(parsed, null, 2);
|
|
67
|
+
console.log(highlight(formatted, { language: 'json', ignoreIllegals: true }));
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
console.log(content);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Render markdown with colored terminal output
|
|
75
|
+
*/
|
|
76
|
+
function renderMarkdown(content) {
|
|
77
|
+
let rendered = content;
|
|
78
|
+
// Code blocks
|
|
79
|
+
rendered = rendered.replace(/```(\w*)\n([\s\S]*?)```/g, (_match, lang, code) => {
|
|
80
|
+
const trimmedCode = code.trim();
|
|
81
|
+
if (lang && lang !== '') {
|
|
82
|
+
try {
|
|
83
|
+
return '\n' + highlight(trimmedCode, { language: lang, ignoreIllegals: true }) + '\n';
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return '\n' + chalk.gray(trimmedCode) + '\n';
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return '\n' + chalk.gray(trimmedCode) + '\n';
|
|
90
|
+
});
|
|
91
|
+
// Links
|
|
92
|
+
rendered = rendered.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_m, text, url) => chalk.blue.underline(text) + chalk.dim(` (${url})`));
|
|
93
|
+
// Headers
|
|
94
|
+
rendered = rendered
|
|
95
|
+
.replace(/^### (.+)$/gm, (_m, h) => '\n' + chalk.cyan(' ' + h) + '\n ' + chalk.dim('-'.repeat(20)))
|
|
96
|
+
.replace(/^## (.+)$/gm, (_m, h) => '\n' + chalk.yellow.bold(' ' + h) + '\n ' + chalk.dim('='.repeat(30)))
|
|
97
|
+
.replace(/^# (.+)$/gm, (_m, h) => '\n' + chalk.magenta.bold(h) + '\n' + chalk.dim('='.repeat(40)));
|
|
98
|
+
// Blockquotes
|
|
99
|
+
rendered = rendered.replace(/^> (.+)$/gm, (_m, quote) => chalk.dim('│ ') + chalk.italic(quote));
|
|
100
|
+
// Horizontal rules
|
|
101
|
+
rendered = rendered.replace(/^---+$/gm, chalk.dim('─'.repeat(40)));
|
|
102
|
+
// Lists
|
|
103
|
+
rendered = rendered.replace(/^- /gm, chalk.dim(' • '));
|
|
104
|
+
rendered = rendered.replace(/^(\d+)\. /gm, (_m, num) => chalk.dim(` ${num}. `));
|
|
105
|
+
// Bold and italic
|
|
106
|
+
rendered = rendered.replace(/\*\*(.+?)\*\*/g, (_m, text) => chalk.bold(text));
|
|
107
|
+
rendered = rendered.replace(/\*(.+?)\*/g, (_m, text) => chalk.italic(text));
|
|
108
|
+
rendered = rendered.replace(/_(.+?)_/g, (_m, text) => chalk.italic(text));
|
|
109
|
+
// Inline code
|
|
110
|
+
rendered = rendered.replace(/`([^`]+)`/g, (_m, code) => chalk.cyan(code));
|
|
111
|
+
console.log(rendered);
|
|
112
|
+
}
|
|
113
|
+
function renderYaml(content) {
|
|
114
|
+
try {
|
|
115
|
+
console.log(highlight(content, { language: 'yaml', ignoreIllegals: true }));
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
console.log(content);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function renderXml(content) {
|
|
122
|
+
try {
|
|
123
|
+
console.log(highlight(content, { language: 'xml', ignoreIllegals: true }));
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
console.log(content);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function renderCode(content, lang) {
|
|
130
|
+
try {
|
|
131
|
+
if (lang) {
|
|
132
|
+
console.log(highlight(content, { language: lang, ignoreIllegals: true }));
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
console.log(highlight(content, { ignoreIllegals: true }));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
console.log(content);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function formatDataWithHint(data, format) {
|
|
143
|
+
switch (format) {
|
|
144
|
+
case 'primitive':
|
|
145
|
+
renderPrimitive(data);
|
|
146
|
+
break;
|
|
147
|
+
case 'list':
|
|
148
|
+
renderList(Array.isArray(data) ? data : [data]);
|
|
149
|
+
break;
|
|
150
|
+
case 'table':
|
|
151
|
+
renderTable(data);
|
|
152
|
+
break;
|
|
153
|
+
case 'tree':
|
|
154
|
+
renderTree(data);
|
|
155
|
+
break;
|
|
156
|
+
case 'card':
|
|
157
|
+
renderCard(data);
|
|
158
|
+
break;
|
|
159
|
+
case 'tabs':
|
|
160
|
+
renderTabs(data);
|
|
161
|
+
break;
|
|
162
|
+
case 'accordion':
|
|
163
|
+
renderAccordion(data);
|
|
164
|
+
break;
|
|
165
|
+
case 'none':
|
|
166
|
+
renderNone();
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Detect format type from data structure
|
|
172
|
+
*/
|
|
173
|
+
export function detectFormat(data) {
|
|
174
|
+
if (data === null || data === undefined) {
|
|
175
|
+
return 'none';
|
|
176
|
+
}
|
|
177
|
+
if (typeof data === 'string' || typeof data === 'number' || typeof data === 'boolean') {
|
|
178
|
+
return 'primitive';
|
|
179
|
+
}
|
|
180
|
+
if (Array.isArray(data)) {
|
|
181
|
+
if (data.length === 0)
|
|
182
|
+
return 'list';
|
|
183
|
+
const firstItem = data[0];
|
|
184
|
+
if (typeof firstItem !== 'object' || firstItem === null)
|
|
185
|
+
return 'list';
|
|
186
|
+
if (isFlatObject(firstItem))
|
|
187
|
+
return 'table';
|
|
188
|
+
return 'tree';
|
|
189
|
+
}
|
|
190
|
+
if (typeof data === 'object') {
|
|
191
|
+
if (isFlatObject(data))
|
|
192
|
+
return 'table';
|
|
193
|
+
return 'tree';
|
|
194
|
+
}
|
|
195
|
+
return 'none';
|
|
196
|
+
}
|
|
197
|
+
function isFlatObject(obj) {
|
|
198
|
+
if (typeof obj !== 'object' || obj === null)
|
|
199
|
+
return false;
|
|
200
|
+
for (const value of Object.values(obj)) {
|
|
201
|
+
if (typeof value === 'object' && value !== null)
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
export function renderPrimitive(value) {
|
|
207
|
+
if (typeof value === 'boolean') {
|
|
208
|
+
console.log(value ? 'yes' : 'no');
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
console.log(value);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
export function renderList(data) {
|
|
215
|
+
if (data.length === 0) {
|
|
216
|
+
console.log('(empty)');
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
// Check if items are objects - if so, render as table instead
|
|
220
|
+
const firstItem = data[0];
|
|
221
|
+
if (typeof firstItem === 'object' && firstItem !== null && !Array.isArray(firstItem)) {
|
|
222
|
+
// For arrays of objects, delegate to table rendering for better display
|
|
223
|
+
renderTable(data);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
// Simple list for primitives
|
|
227
|
+
data.forEach(item => console.log(` * ${item}`));
|
|
228
|
+
}
|
|
229
|
+
export function renderTable(data) {
|
|
230
|
+
if (!Array.isArray(data)) {
|
|
231
|
+
const entries = Object.entries(data).filter(([key, value]) => !(key === 'returnValue' && value === true));
|
|
232
|
+
if (entries.length === 0) {
|
|
233
|
+
console.log('(empty)');
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const maxKeyLength = Math.max(...entries.map(([k]) => formatKey(k).length));
|
|
237
|
+
const maxValueLength = Math.max(...entries.map(([_, v]) => String(formatValue(v)).length));
|
|
238
|
+
console.log(`┌─${'─'.repeat(maxKeyLength)}─┬─${'─'.repeat(maxValueLength)}─┐`);
|
|
239
|
+
for (let i = 0; i < entries.length; i++) {
|
|
240
|
+
const [key, value] = entries[i];
|
|
241
|
+
const formattedKey = formatKey(key);
|
|
242
|
+
const formattedValue = String(formatValue(value));
|
|
243
|
+
const keyPadding = ' '.repeat(maxKeyLength - formattedKey.length);
|
|
244
|
+
const valuePadding = ' '.repeat(maxValueLength - formattedValue.length);
|
|
245
|
+
console.log(`| ${formattedKey}${keyPadding} | ${formattedValue}${valuePadding} |`);
|
|
246
|
+
if (i < entries.length - 1) {
|
|
247
|
+
console.log(`├─${'─'.repeat(maxKeyLength)}─┼─${'─'.repeat(maxValueLength)}─┤`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
console.log(`└─${'─'.repeat(maxKeyLength)}─┴─${'─'.repeat(maxValueLength)}─┘`);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
if (data.length === 0) {
|
|
254
|
+
console.log('(empty)');
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const allKeys = Array.from(new Set(data.flatMap(obj => Object.keys(obj)))).filter(k => k !== 'returnValue');
|
|
258
|
+
if (allKeys.length === 0) {
|
|
259
|
+
console.log('(no data)');
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const columnWidths = new Map();
|
|
263
|
+
for (const key of allKeys) {
|
|
264
|
+
const headerWidth = formatKey(key).length;
|
|
265
|
+
const maxValueWidth = Math.max(...data.map(obj => String(formatValue(obj[key] ?? '')).length));
|
|
266
|
+
columnWidths.set(key, Math.max(headerWidth, maxValueWidth));
|
|
267
|
+
}
|
|
268
|
+
const topBorderParts = allKeys.map(key => '─'.repeat(columnWidths.get(key) + 2));
|
|
269
|
+
console.log('┌' + topBorderParts.join('┬') + '┐');
|
|
270
|
+
const headerParts = allKeys.map(key => {
|
|
271
|
+
const formattedKey = formatKey(key);
|
|
272
|
+
const width = columnWidths.get(key);
|
|
273
|
+
return ' ' + formattedKey.padEnd(width) + ' ';
|
|
274
|
+
});
|
|
275
|
+
console.log('│' + headerParts.join('│') + '│');
|
|
276
|
+
const separatorParts = allKeys.map(key => '─'.repeat(columnWidths.get(key) + 2));
|
|
277
|
+
console.log('├' + separatorParts.join('┼') + '┤');
|
|
278
|
+
for (const row of data) {
|
|
279
|
+
const rowParts = allKeys.map(key => {
|
|
280
|
+
const value = formatValue(row[key] ?? '');
|
|
281
|
+
const width = columnWidths.get(key);
|
|
282
|
+
return ' ' + String(value).padEnd(width) + ' ';
|
|
283
|
+
});
|
|
284
|
+
console.log('│' + rowParts.join('│') + '│');
|
|
285
|
+
}
|
|
286
|
+
const bottomBorderParts = allKeys.map(key => '─'.repeat(columnWidths.get(key) + 2));
|
|
287
|
+
console.log('└' + bottomBorderParts.join('┴') + '┘');
|
|
288
|
+
}
|
|
289
|
+
export function renderTree(data, indent = '') {
|
|
290
|
+
if (Array.isArray(data)) {
|
|
291
|
+
if (data.length === 0) {
|
|
292
|
+
console.log(`${indent}(empty)`);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
data.forEach((item, index) => {
|
|
296
|
+
if (typeof item === 'object' && item !== null) {
|
|
297
|
+
console.log(`${indent}[${index}]`);
|
|
298
|
+
renderTree(item, indent + ' ');
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
console.log(`${indent}* ${item}`);
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
if (typeof data === 'object' && data !== null) {
|
|
307
|
+
const entries = Object.entries(data).filter(([key, value]) => !(key === 'returnValue' && value === true));
|
|
308
|
+
for (const [key, value] of entries) {
|
|
309
|
+
const formattedKey = formatKey(key);
|
|
310
|
+
if (value === null || value === undefined) {
|
|
311
|
+
console.log(`${indent}${formattedKey}: (none)`);
|
|
312
|
+
}
|
|
313
|
+
else if (Array.isArray(value)) {
|
|
314
|
+
console.log(`${indent}${formattedKey}:`);
|
|
315
|
+
renderTree(value, indent + ' ');
|
|
316
|
+
}
|
|
317
|
+
else if (typeof value === 'object') {
|
|
318
|
+
console.log(`${indent}${formattedKey}:`);
|
|
319
|
+
renderTree(value, indent + ' ');
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
console.log(`${indent}${formattedKey}: ${formatValue(value)}`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
console.log(`${indent}${formatValue(data)}`);
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Render data as a bordered card
|
|
331
|
+
* Great for single objects with mixed content types
|
|
332
|
+
*/
|
|
333
|
+
export function renderCard(data) {
|
|
334
|
+
if (Array.isArray(data)) {
|
|
335
|
+
// Multiple cards
|
|
336
|
+
data.forEach((item, index) => {
|
|
337
|
+
if (index > 0)
|
|
338
|
+
console.log('');
|
|
339
|
+
renderSingleCard(item);
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
renderSingleCard(data);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
function renderSingleCard(data) {
|
|
347
|
+
if (typeof data !== 'object' || data === null) {
|
|
348
|
+
console.log(data);
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
const entries = Object.entries(data).filter(([key]) => key !== 'returnValue');
|
|
352
|
+
if (entries.length === 0) {
|
|
353
|
+
console.log('(empty)');
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
// Calculate width based on content
|
|
357
|
+
const maxKeyLength = Math.max(...entries.map(([k]) => formatKey(k).length));
|
|
358
|
+
const width = Math.min(Math.max(maxKeyLength + 40, 50), 80);
|
|
359
|
+
// Card top border
|
|
360
|
+
console.log(chalk.dim('┌' + '─'.repeat(width - 2) + '┐'));
|
|
361
|
+
// Card content
|
|
362
|
+
for (const [key, value] of entries) {
|
|
363
|
+
const formattedKey = chalk.bold(formatKey(key));
|
|
364
|
+
if (Array.isArray(value)) {
|
|
365
|
+
// Render arrays inline or as bullets
|
|
366
|
+
console.log(chalk.dim('│') + ` ${formattedKey}:`);
|
|
367
|
+
if (value.length === 0) {
|
|
368
|
+
console.log(chalk.dim('│') + ' (empty)');
|
|
369
|
+
}
|
|
370
|
+
else if (typeof value[0] === 'object') {
|
|
371
|
+
// Array of objects - render as mini-table
|
|
372
|
+
value.forEach((item, idx) => {
|
|
373
|
+
const summary = typeof item === 'object'
|
|
374
|
+
? Object.entries(item).slice(0, 2).map(([k, v]) => `${formatKey(k)}: ${formatValue(v)}`).join(', ')
|
|
375
|
+
: String(item);
|
|
376
|
+
console.log(chalk.dim('│') + ` ${chalk.cyan(`[${idx}]`)} ${summary}`);
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
// Array of primitives - render as bullets
|
|
381
|
+
value.forEach(item => {
|
|
382
|
+
console.log(chalk.dim('│') + ` • ${formatValue(item)}`);
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
else if (typeof value === 'object' && value !== null) {
|
|
387
|
+
// Nested object - render inline
|
|
388
|
+
console.log(chalk.dim('│') + ` ${formattedKey}:`);
|
|
389
|
+
for (const [subKey, subVal] of Object.entries(value)) {
|
|
390
|
+
console.log(chalk.dim('│') + ` ${formatKey(subKey)}: ${formatValue(subVal)}`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
// Simple value
|
|
395
|
+
const formattedValue = formatCardValue(value);
|
|
396
|
+
console.log(chalk.dim('│') + ` ${formattedKey}: ${formattedValue}`);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
// Card bottom border
|
|
400
|
+
console.log(chalk.dim('└' + '─'.repeat(width - 2) + '┘'));
|
|
401
|
+
}
|
|
402
|
+
function formatCardValue(value) {
|
|
403
|
+
if (value === null || value === undefined)
|
|
404
|
+
return chalk.dim('-');
|
|
405
|
+
if (typeof value === 'boolean')
|
|
406
|
+
return value ? chalk.green('yes') : chalk.red('no');
|
|
407
|
+
if (typeof value === 'number')
|
|
408
|
+
return chalk.yellow(String(value));
|
|
409
|
+
if (typeof value === 'string') {
|
|
410
|
+
// Check for URLs
|
|
411
|
+
if (value.startsWith('http://') || value.startsWith('https://')) {
|
|
412
|
+
return chalk.blue.underline(value);
|
|
413
|
+
}
|
|
414
|
+
// Check for dates
|
|
415
|
+
if (/^\d{4}-\d{2}-\d{2}/.test(value)) {
|
|
416
|
+
return chalk.cyan(value);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return String(value);
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Render data as tabbed sections
|
|
423
|
+
* Each top-level key becomes a tab
|
|
424
|
+
*/
|
|
425
|
+
export function renderTabs(data) {
|
|
426
|
+
if (typeof data !== 'object' || data === null || Array.isArray(data)) {
|
|
427
|
+
console.log('Tabs format requires an object with sections');
|
|
428
|
+
renderTree(data);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
const sections = Object.entries(data).filter(([key]) => key !== 'returnValue');
|
|
432
|
+
sections.forEach(([title, content], index) => {
|
|
433
|
+
// Tab separator
|
|
434
|
+
if (index > 0) {
|
|
435
|
+
console.log('\n' + chalk.dim('─'.repeat(60)));
|
|
436
|
+
}
|
|
437
|
+
// Tab header
|
|
438
|
+
console.log(chalk.bold.cyan(`\n▸ ${formatKey(title)}`));
|
|
439
|
+
console.log('');
|
|
440
|
+
// Tab content
|
|
441
|
+
if (content === null || content === undefined) {
|
|
442
|
+
console.log(' (empty)');
|
|
443
|
+
}
|
|
444
|
+
else if (Array.isArray(content)) {
|
|
445
|
+
if (content.length === 0) {
|
|
446
|
+
console.log(' (empty)');
|
|
447
|
+
}
|
|
448
|
+
else if (typeof content[0] === 'object') {
|
|
449
|
+
// Array of objects - render as table
|
|
450
|
+
renderTable(content);
|
|
451
|
+
}
|
|
452
|
+
else {
|
|
453
|
+
// Array of primitives - render as list
|
|
454
|
+
content.forEach(item => console.log(` • ${formatValue(item)}`));
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
else if (typeof content === 'object') {
|
|
458
|
+
// Nested object - render as indented tree
|
|
459
|
+
renderTree(content, ' ');
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
console.log(` ${formatValue(content)}`);
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Render data as collapsible accordion sections
|
|
468
|
+
* Similar to tabs but with visual collapse indicators
|
|
469
|
+
*/
|
|
470
|
+
export function renderAccordion(data) {
|
|
471
|
+
if (Array.isArray(data)) {
|
|
472
|
+
// Array of items - each becomes an accordion section
|
|
473
|
+
data.forEach((item, index) => {
|
|
474
|
+
if (index > 0)
|
|
475
|
+
console.log('');
|
|
476
|
+
if (typeof item === 'object' && item !== null) {
|
|
477
|
+
// Use title/name/label field as header, or index
|
|
478
|
+
const title = item.title || item.name || item.label || `Item ${index + 1}`;
|
|
479
|
+
const content = item.content || item.data || item;
|
|
480
|
+
console.log(chalk.bold(`▼ ${title}`));
|
|
481
|
+
if (typeof content === 'object') {
|
|
482
|
+
// Filter out the title field from display
|
|
483
|
+
const filtered = Object.fromEntries(Object.entries(content).filter(([k]) => !['title', 'name', 'label'].includes(k)));
|
|
484
|
+
renderTree(filtered, ' ');
|
|
485
|
+
}
|
|
486
|
+
else {
|
|
487
|
+
console.log(` ${formatValue(content)}`);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
else {
|
|
491
|
+
console.log(`▼ ${formatValue(item)}`);
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
else if (typeof data === 'object' && data !== null) {
|
|
496
|
+
// Object - each key becomes an accordion section
|
|
497
|
+
const entries = Object.entries(data).filter(([key]) => key !== 'returnValue');
|
|
498
|
+
entries.forEach(([key, value], index) => {
|
|
499
|
+
if (index > 0)
|
|
500
|
+
console.log('');
|
|
501
|
+
console.log(chalk.bold(`▼ ${formatKey(key)}`));
|
|
502
|
+
if (value === null || value === undefined) {
|
|
503
|
+
console.log(' (empty)');
|
|
504
|
+
}
|
|
505
|
+
else if (Array.isArray(value)) {
|
|
506
|
+
value.forEach(item => {
|
|
507
|
+
if (typeof item === 'object') {
|
|
508
|
+
renderTree(item, ' ');
|
|
509
|
+
}
|
|
510
|
+
else {
|
|
511
|
+
console.log(` • ${formatValue(item)}`);
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
else if (typeof value === 'object') {
|
|
516
|
+
renderTree(value, ' ');
|
|
517
|
+
}
|
|
518
|
+
else {
|
|
519
|
+
console.log(` ${formatValue(value)}`);
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
else {
|
|
524
|
+
console.log(formatValue(data));
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
export function renderNone() {
|
|
528
|
+
console.log('Done');
|
|
529
|
+
}
|
|
530
|
+
export function formatKey(key) {
|
|
531
|
+
return key
|
|
532
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
|
|
533
|
+
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
534
|
+
.replace(/_/g, ' ')
|
|
535
|
+
.replace(/^./, str => str.toUpperCase())
|
|
536
|
+
.trim();
|
|
537
|
+
}
|
|
538
|
+
export function formatValue(value) {
|
|
539
|
+
if (value === null || value === undefined)
|
|
540
|
+
return '-';
|
|
541
|
+
if (typeof value === 'boolean')
|
|
542
|
+
return value ? 'yes' : 'no';
|
|
543
|
+
return value;
|
|
544
|
+
}
|
|
545
|
+
export const STATUS = {
|
|
546
|
+
OK: 'ok',
|
|
547
|
+
UPDATE: 'update',
|
|
548
|
+
WARN: 'warn',
|
|
549
|
+
ERROR: '!',
|
|
550
|
+
OFF: 'off',
|
|
551
|
+
UNKNOWN: '?',
|
|
552
|
+
};
|
|
553
|
+
export function formatToMimeType(format) {
|
|
554
|
+
const mimeTypes = {
|
|
555
|
+
json: 'application/json',
|
|
556
|
+
markdown: 'text/markdown',
|
|
557
|
+
yaml: 'text/yaml',
|
|
558
|
+
xml: 'application/xml',
|
|
559
|
+
html: 'text/html',
|
|
560
|
+
};
|
|
561
|
+
if (mimeTypes[format])
|
|
562
|
+
return mimeTypes[format];
|
|
563
|
+
if (format === 'code')
|
|
564
|
+
return 'text/plain';
|
|
565
|
+
if (format.startsWith('code:'))
|
|
566
|
+
return `text/x-${format.split(':')[1]}`;
|
|
567
|
+
return undefined;
|
|
568
|
+
}
|
|
569
|
+
export function printSuccess(message) {
|
|
570
|
+
console.error(`✓ ${message}`);
|
|
571
|
+
}
|
|
572
|
+
export function printError(message) {
|
|
573
|
+
console.error(`✗ ${message}`);
|
|
574
|
+
}
|
|
575
|
+
export function printInfo(message) {
|
|
576
|
+
console.error(`${message}`);
|
|
577
|
+
}
|
|
578
|
+
export function printWarning(message) {
|
|
579
|
+
console.error(`! ${message}`);
|
|
580
|
+
}
|
|
581
|
+
export function printHeader(title) {
|
|
582
|
+
console.error(`\n${title}`);
|
|
583
|
+
console.error('─'.repeat(title.length));
|
|
584
|
+
}
|
|
585
|
+
//# sourceMappingURL=cli-formatter.js.map
|