@msn-control/liftoff 0.4.0 → 0.4.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/README.md +32 -0
- package/dist/args.d.ts +33 -0
- package/dist/args.js +93 -37
- package/dist/args.js.map +1 -1
- package/dist/cli.js +6 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands.d.ts +4 -0
- package/dist/commands.js +401 -280
- package/dist/commands.js.map +1 -1
- package/dist/framework-adapters.d.ts +5 -2
- package/dist/framework-adapters.js +5 -2
- package/dist/framework-adapters.js.map +1 -1
- package/dist/interactive.d.ts +43 -6
- package/dist/interactive.js +180 -140
- package/dist/interactive.js.map +1 -1
- package/dist/planner.d.ts +5 -0
- package/dist/planner.js +27 -20
- package/dist/planner.js.map +1 -1
- package/dist/process-runner.js +9 -6
- package/dist/process-runner.js.map +1 -1
- package/dist/project-dependencies.d.ts +6 -4
- package/dist/project-dependencies.js +4 -3
- package/dist/project-dependencies.js.map +1 -1
- package/dist/published-verifier.js +1 -1
- package/dist/published-verifier.js.map +1 -1
- package/dist/terminal.d.ts +99 -3
- package/dist/terminal.js +482 -66
- package/dist/terminal.js.map +1 -1
- package/package.json +1 -1
package/dist/terminal.js
CHANGED
|
@@ -1,40 +1,146 @@
|
|
|
1
1
|
import picocolors, { createColors } from 'picocolors';
|
|
2
|
+
export const TERMINAL_LAYOUT = {
|
|
3
|
+
compactColumns: 64,
|
|
4
|
+
fullColumns: 96,
|
|
5
|
+
maximumContentColumns: 92,
|
|
6
|
+
minimumColumns: 20,
|
|
7
|
+
indent: 2,
|
|
8
|
+
sectionSpacing: 1
|
|
9
|
+
};
|
|
10
|
+
export const TERMINAL_GLYPHS = {
|
|
11
|
+
topLeft: '┌',
|
|
12
|
+
topRight: '┐',
|
|
13
|
+
bottomLeft: '└',
|
|
14
|
+
bottomRight: '┘',
|
|
15
|
+
horizontal: '─',
|
|
16
|
+
vertical: '│',
|
|
17
|
+
leftJunction: '├',
|
|
18
|
+
rightJunction: '┤',
|
|
19
|
+
bullet: '•',
|
|
20
|
+
choice: '›',
|
|
21
|
+
selected: '●',
|
|
22
|
+
unselected: '○'
|
|
23
|
+
};
|
|
2
24
|
export const LIFTOFF_WORDMARK = [
|
|
3
|
-
'
|
|
4
|
-
'
|
|
5
|
-
'
|
|
6
|
-
'
|
|
7
|
-
'
|
|
25
|
+
'██╗ ██╗███████╗████████╗ ██████╗ ███████╗███████╗',
|
|
26
|
+
'██║ ██║██╔════╝╚══██╔══╝██╔═══██╗██╔════╝██╔════╝',
|
|
27
|
+
'██║ ██║█████╗ ██║ ██║ ██║█████╗ █████╗',
|
|
28
|
+
'██║ ██║██╔══╝ ██║ ██║ ██║██╔══╝ ██╔══╝',
|
|
29
|
+
'███████╗██║██║ ██║ ╚██████╔╝██║ ██║',
|
|
30
|
+
'╚══════╝╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝'
|
|
31
|
+
];
|
|
32
|
+
export const LIFTOFF_COMPACT_WORDMARK = [
|
|
33
|
+
'╻ ╻┏━╸╺┳╸┏━┓┏━╸┏━╸',
|
|
34
|
+
'┃ ┃┣╸ ┃ ┃ ┃┣╸ ┣╸',
|
|
35
|
+
'┗━╸╹╹ ╹ ┗━┛╹ ╹'
|
|
8
36
|
];
|
|
9
37
|
const ANSI_PATTERN = /\u001B\[[0-?]*[ -/]*[@-~]/g;
|
|
38
|
+
const ANSI_ANY_PATTERN = /\u001B\[[0-?]*[ -/]*[@-~]/;
|
|
39
|
+
const ANSI_AT_START_PATTERN = /^\u001B\[[0-?]*[ -/]*[@-~]/;
|
|
40
|
+
export function stripAnsi(value) {
|
|
41
|
+
return value.replace(ANSI_PATTERN, '');
|
|
42
|
+
}
|
|
43
|
+
function codePointWidth(character) {
|
|
44
|
+
const codePoint = character.codePointAt(0);
|
|
45
|
+
if (codePoint === undefined || codePoint === 0) {
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
if (codePoint < 32 ||
|
|
49
|
+
codePoint >= 0x7f && codePoint < 0xa0 ||
|
|
50
|
+
codePoint >= 0x300 && codePoint <= 0x36f ||
|
|
51
|
+
codePoint >= 0x1ab0 && codePoint <= 0x1aff ||
|
|
52
|
+
codePoint >= 0x1dc0 && codePoint <= 0x1dff ||
|
|
53
|
+
codePoint >= 0x20d0 && codePoint <= 0x20ff ||
|
|
54
|
+
codePoint >= 0xfe20 && codePoint <= 0xfe2f ||
|
|
55
|
+
codePoint >= 0xfe00 && codePoint <= 0xfe0f ||
|
|
56
|
+
codePoint >= 0x1f3fb && codePoint <= 0x1f3ff) {
|
|
57
|
+
return 0;
|
|
58
|
+
}
|
|
59
|
+
if (codePoint >= 0x1100 && (codePoint <= 0x115f ||
|
|
60
|
+
codePoint === 0x2329 ||
|
|
61
|
+
codePoint === 0x232a ||
|
|
62
|
+
codePoint >= 0x2e80 && codePoint <= 0xa4cf && codePoint !== 0x303f ||
|
|
63
|
+
codePoint >= 0xac00 && codePoint <= 0xd7a3 ||
|
|
64
|
+
codePoint >= 0xf900 && codePoint <= 0xfaff ||
|
|
65
|
+
codePoint >= 0xfe10 && codePoint <= 0xfe19 ||
|
|
66
|
+
codePoint >= 0xfe30 && codePoint <= 0xfe6f ||
|
|
67
|
+
codePoint >= 0xff00 && codePoint <= 0xff60 ||
|
|
68
|
+
codePoint >= 0xffe0 && codePoint <= 0xffe6 ||
|
|
69
|
+
codePoint >= 0x1f300 && codePoint <= 0x1faff ||
|
|
70
|
+
codePoint >= 0x20000 && codePoint <= 0x3fffd)) {
|
|
71
|
+
return 2;
|
|
72
|
+
}
|
|
73
|
+
return 1;
|
|
74
|
+
}
|
|
10
75
|
export function visibleLength(value) {
|
|
11
|
-
return value.
|
|
76
|
+
return Array.from(stripAnsi(value)).reduce((total, character) => total + (character === '\t' ? 4 : codePointWidth(character)), 0);
|
|
12
77
|
}
|
|
13
|
-
function padVisible(value, width) {
|
|
78
|
+
export function padVisible(value, width) {
|
|
14
79
|
return `${value}${' '.repeat(Math.max(0, width - visibleLength(value)))}`;
|
|
15
80
|
}
|
|
16
|
-
function
|
|
17
|
-
|
|
81
|
+
function takeVisible(value, width) {
|
|
82
|
+
let output = '';
|
|
83
|
+
let used = 0;
|
|
84
|
+
for (const character of Array.from(value)) {
|
|
85
|
+
const characterWidth = codePointWidth(character);
|
|
86
|
+
if (used + characterWidth > width) {
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
output += character;
|
|
90
|
+
used += characterWidth;
|
|
91
|
+
}
|
|
92
|
+
return output;
|
|
93
|
+
}
|
|
94
|
+
function truncateVisible(value, width) {
|
|
95
|
+
const plain = stripAnsi(value);
|
|
96
|
+
if (visibleLength(plain) <= width) {
|
|
97
|
+
return plain;
|
|
98
|
+
}
|
|
99
|
+
if (width <= 1) {
|
|
100
|
+
return takeVisible(plain, width);
|
|
101
|
+
}
|
|
102
|
+
return `${takeVisible(plain, width - 1)}…`;
|
|
103
|
+
}
|
|
104
|
+
function splitLongWord(word, width) {
|
|
105
|
+
const chunks = [];
|
|
106
|
+
let remaining = word;
|
|
107
|
+
while (remaining) {
|
|
108
|
+
const chunk = takeVisible(remaining, width);
|
|
109
|
+
if (!chunk) {
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
chunks.push(chunk);
|
|
113
|
+
remaining = remaining.slice(chunk.length);
|
|
114
|
+
}
|
|
115
|
+
return chunks.length > 0 ? chunks : [''];
|
|
116
|
+
}
|
|
117
|
+
function wrapPlainLine(value, width, breakLongWords) {
|
|
118
|
+
if (visibleLength(value) <= width) {
|
|
18
119
|
return [value];
|
|
19
120
|
}
|
|
20
|
-
const words = value.split(/\s+/);
|
|
121
|
+
const words = value.trim().split(/\s+/);
|
|
21
122
|
const lines = [];
|
|
22
123
|
let current = '';
|
|
23
124
|
for (const word of words) {
|
|
24
|
-
if (word
|
|
125
|
+
if (visibleLength(word) > width) {
|
|
25
126
|
if (current) {
|
|
26
127
|
lines.push(current);
|
|
27
128
|
current = '';
|
|
28
129
|
}
|
|
29
|
-
|
|
30
|
-
|
|
130
|
+
if (breakLongWords) {
|
|
131
|
+
const chunks = splitLongWord(word, width);
|
|
132
|
+
lines.push(...chunks.slice(0, -1));
|
|
133
|
+
current = chunks.at(-1) ?? '';
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
current = word;
|
|
31
137
|
}
|
|
32
138
|
continue;
|
|
33
139
|
}
|
|
34
140
|
if (!current) {
|
|
35
141
|
current = word;
|
|
36
142
|
}
|
|
37
|
-
else if (current
|
|
143
|
+
else if (visibleLength(`${current} ${word}`) <= width) {
|
|
38
144
|
current += ` ${word}`;
|
|
39
145
|
}
|
|
40
146
|
else {
|
|
@@ -42,10 +148,68 @@ function wrapLine(value, width) {
|
|
|
42
148
|
current = word;
|
|
43
149
|
}
|
|
44
150
|
}
|
|
45
|
-
if (current) {
|
|
151
|
+
if (current || lines.length === 0) {
|
|
46
152
|
lines.push(current);
|
|
47
153
|
}
|
|
48
|
-
return lines
|
|
154
|
+
return lines;
|
|
155
|
+
}
|
|
156
|
+
function wrapStyledLine(value, width) {
|
|
157
|
+
const lines = [];
|
|
158
|
+
let line = '';
|
|
159
|
+
let used = 0;
|
|
160
|
+
let index = 0;
|
|
161
|
+
while (index < value.length) {
|
|
162
|
+
const ansi = value.slice(index).match(ANSI_AT_START_PATTERN)?.[0];
|
|
163
|
+
if (ansi) {
|
|
164
|
+
line += ansi;
|
|
165
|
+
index += ansi.length;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
const codePoint = value.codePointAt(index);
|
|
169
|
+
if (codePoint === undefined) {
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
const character = String.fromCodePoint(codePoint);
|
|
173
|
+
const characterWidth = character === '\t' ? 4 : codePointWidth(character);
|
|
174
|
+
if (used > 0 && used + characterWidth > width) {
|
|
175
|
+
lines.push(line.trimEnd());
|
|
176
|
+
line = '';
|
|
177
|
+
used = 0;
|
|
178
|
+
if (/\s/.test(character)) {
|
|
179
|
+
index += character.length;
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
line += character;
|
|
184
|
+
used += characterWidth;
|
|
185
|
+
index += character.length;
|
|
186
|
+
}
|
|
187
|
+
if (line || lines.length === 0) {
|
|
188
|
+
lines.push(line.trimEnd());
|
|
189
|
+
}
|
|
190
|
+
return lines;
|
|
191
|
+
}
|
|
192
|
+
export function wrapVisible(value, width, breakLongWords = false) {
|
|
193
|
+
const safeWidth = Math.max(1, width);
|
|
194
|
+
return value.split(/\r?\n/).flatMap((line) => ANSI_ANY_PATTERN.test(line)
|
|
195
|
+
? wrapStyledLine(line, safeWidth)
|
|
196
|
+
: wrapPlainLine(line, safeWidth, breakLongWords));
|
|
197
|
+
}
|
|
198
|
+
function fitColumnWidths(natural, available) {
|
|
199
|
+
const widths = natural.map((width) => Math.max(4, width));
|
|
200
|
+
while (widths.reduce((total, width) => total + width, 0) > available) {
|
|
201
|
+
let largestIndex = -1;
|
|
202
|
+
for (let index = 0; index < widths.length; index += 1) {
|
|
203
|
+
if (widths[index] > 4 && (largestIndex === -1 || widths[index] > widths[largestIndex])) {
|
|
204
|
+
largestIndex = index;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (largestIndex === -1) {
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
widths[largestIndex] -= 1;
|
|
211
|
+
}
|
|
212
|
+
return widths;
|
|
49
213
|
}
|
|
50
214
|
export class TerminalRenderer {
|
|
51
215
|
options;
|
|
@@ -58,20 +222,20 @@ export class TerminalRenderer {
|
|
|
58
222
|
this.options = options;
|
|
59
223
|
const stream = options.stream;
|
|
60
224
|
const env = options.env ?? process.env;
|
|
61
|
-
this.columns = Math.max(
|
|
225
|
+
this.columns = Math.max(TERMINAL_LAYOUT.minimumColumns, options.columns ?? stream.columns ?? 80);
|
|
62
226
|
this.jsonMode = options.json ?? false;
|
|
63
227
|
const tty = stream.isTTY === true;
|
|
64
228
|
const noColor = Object.hasOwn(env, 'NO_COLOR');
|
|
65
229
|
this.colorEnabled = !this.jsonMode && !options.snapshot && !noColor &&
|
|
66
|
-
(options.color ?? (tty &&
|
|
230
|
+
(options.color ?? (tty && picocolors.isColorSupported));
|
|
67
231
|
this.colors = createColors(this.colorEnabled);
|
|
68
|
-
this.layout = this.jsonMode || (!tty && !options.snapshot)
|
|
232
|
+
this.layout = options.layout ?? (this.jsonMode || (!tty && !options.snapshot)
|
|
69
233
|
? 'plain'
|
|
70
|
-
: this.columns >=
|
|
234
|
+
: this.columns >= TERMINAL_LAYOUT.fullColumns
|
|
71
235
|
? 'full'
|
|
72
|
-
: this.columns >=
|
|
236
|
+
: this.columns >= TERMINAL_LAYOUT.compactColumns
|
|
73
237
|
? 'compact'
|
|
74
|
-
: 'plain';
|
|
238
|
+
: 'plain');
|
|
75
239
|
}
|
|
76
240
|
write(value) {
|
|
77
241
|
if (value) {
|
|
@@ -81,102 +245,354 @@ export class TerminalRenderer {
|
|
|
81
245
|
json(value) {
|
|
82
246
|
return `${JSON.stringify(value, null, 2)}\n`;
|
|
83
247
|
}
|
|
248
|
+
style(kind, value) {
|
|
249
|
+
switch (kind) {
|
|
250
|
+
case 'brand':
|
|
251
|
+
case 'info':
|
|
252
|
+
return this.colors.cyan(value);
|
|
253
|
+
case 'success':
|
|
254
|
+
return this.colors.green(value);
|
|
255
|
+
case 'warning':
|
|
256
|
+
return this.colors.yellow(value);
|
|
257
|
+
case 'error':
|
|
258
|
+
return this.colors.red(value);
|
|
259
|
+
case 'command':
|
|
260
|
+
return this.colors.magenta(value);
|
|
261
|
+
case 'pending':
|
|
262
|
+
case 'metadata':
|
|
263
|
+
return this.colors.dim(value);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
contentWidth() {
|
|
267
|
+
return Math.max(1, Math.min(this.columns - 4, TERMINAL_LAYOUT.maximumContentColumns));
|
|
268
|
+
}
|
|
269
|
+
normalize(value) {
|
|
270
|
+
return this.options.normalize?.(value) ?? value;
|
|
271
|
+
}
|
|
84
272
|
banner(subtitle = 'Project workstation and scaffold initializer') {
|
|
85
273
|
if (this.jsonMode) {
|
|
86
274
|
return '';
|
|
87
275
|
}
|
|
88
276
|
if (this.layout === 'plain') {
|
|
89
|
-
return `${this.colors.bold('Liftoff')} - ${subtitle}\n`;
|
|
277
|
+
return `${this.colors.bold('Liftoff')} - ${subtitle}\n\n`;
|
|
90
278
|
}
|
|
91
279
|
if (this.layout === 'compact') {
|
|
92
|
-
return
|
|
280
|
+
return [
|
|
281
|
+
...LIFTOFF_COMPACT_WORDMARK.map((line) => this.colors.bold(this.style('brand', line))),
|
|
282
|
+
...wrapVisible(subtitle, this.columns).map((line) => this.style('metadata', line)),
|
|
283
|
+
''
|
|
284
|
+
].join('\n');
|
|
93
285
|
}
|
|
94
|
-
const
|
|
95
|
-
const border =
|
|
286
|
+
const width = this.contentWidth();
|
|
287
|
+
const border = `${TERMINAL_GLYPHS.topLeft}${TERMINAL_GLYPHS.horizontal.repeat(width + 2)}${TERMINAL_GLYPHS.topRight}`;
|
|
96
288
|
const lines = [
|
|
97
|
-
...LIFTOFF_WORDMARK.map((line) => this.colors.
|
|
289
|
+
...LIFTOFF_WORDMARK.map((line) => this.colors.bold(this.style('brand', line))),
|
|
98
290
|
'',
|
|
99
|
-
this.
|
|
291
|
+
...wrapVisible(subtitle, width, true).map((line) => this.style('metadata', line))
|
|
100
292
|
];
|
|
101
293
|
return [
|
|
102
294
|
border,
|
|
103
|
-
...lines.map((line) =>
|
|
104
|
-
|
|
295
|
+
...lines.map((line) => `${TERMINAL_GLYPHS.vertical} ${padVisible(line, width)} ${TERMINAL_GLYPHS.vertical}`),
|
|
296
|
+
`${TERMINAL_GLYPHS.bottomLeft}${TERMINAL_GLYPHS.horizontal.repeat(width + 2)}${TERMINAL_GLYPHS.bottomRight}`,
|
|
105
297
|
''
|
|
106
298
|
].join('\n');
|
|
107
299
|
}
|
|
300
|
+
commandIdentity(command, description) {
|
|
301
|
+
if (this.jsonMode) {
|
|
302
|
+
return '';
|
|
303
|
+
}
|
|
304
|
+
const identity = `LIFTOFF / ${command.toUpperCase()}`;
|
|
305
|
+
if (this.layout === 'plain') {
|
|
306
|
+
return `${identity} - ${description}\n\n`;
|
|
307
|
+
}
|
|
308
|
+
if (this.layout === 'compact') {
|
|
309
|
+
return `${[
|
|
310
|
+
this.colors.bold(this.style('brand', identity)),
|
|
311
|
+
...wrapVisible(description, this.columns).map((line) => this.style('metadata', line)),
|
|
312
|
+
''
|
|
313
|
+
].join('\n')}\n`;
|
|
314
|
+
}
|
|
315
|
+
return this.panel(identity, [this.style('metadata', description)]);
|
|
316
|
+
}
|
|
108
317
|
heading(value) {
|
|
109
318
|
if (this.jsonMode) {
|
|
110
319
|
return '';
|
|
111
320
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
321
|
+
if (this.layout === 'plain') {
|
|
322
|
+
return `${this.colors.bold(value)}\n`;
|
|
323
|
+
}
|
|
324
|
+
const title = this.colors.bold(this.style('brand', value));
|
|
325
|
+
return `${title}\n${this.style('metadata', TERMINAL_GLYPHS.horizontal.repeat(Math.min(visibleLength(value), this.columns)))}\n`;
|
|
326
|
+
}
|
|
327
|
+
stage(value, detail) {
|
|
328
|
+
if (this.jsonMode) {
|
|
329
|
+
return '';
|
|
330
|
+
}
|
|
331
|
+
const prefix = this.layout === 'plain' ? 'Stage' : '◆';
|
|
332
|
+
const label = this.colors.bold(this.style('info', `${prefix}: ${value}`));
|
|
333
|
+
return `${label}${detail ? `\n${this.style('metadata', detail)}` : ''}\n`;
|
|
115
334
|
}
|
|
116
335
|
panel(title, lines) {
|
|
117
336
|
if (this.jsonMode) {
|
|
118
337
|
return '';
|
|
119
338
|
}
|
|
120
|
-
|
|
121
|
-
|
|
339
|
+
const normalizedTitle = this.normalize(title);
|
|
340
|
+
const bodyLines = (lines.length > 0 ? lines : ['']).map((line) => this.normalize(line));
|
|
341
|
+
if (this.layout === 'plain') {
|
|
342
|
+
return `${this.heading(normalizedTitle)}${bodyLines.flatMap((line) => line.split(/\r?\n/)).map((line) => `${line}\n`).join('')}\n`;
|
|
343
|
+
}
|
|
344
|
+
if (this.layout === 'compact') {
|
|
345
|
+
return `${this.heading(normalizedTitle)}${bodyLines.flatMap((line) => wrapVisible(line, this.columns)).map((line) => `${line}\n`).join('')}\n`;
|
|
122
346
|
}
|
|
123
|
-
const width =
|
|
124
|
-
const
|
|
125
|
-
const
|
|
126
|
-
const
|
|
347
|
+
const width = this.contentWidth();
|
|
348
|
+
const wrapped = bodyLines.flatMap((line) => wrapVisible(line, width, true));
|
|
349
|
+
const safeTitle = truncateVisible(normalizedTitle, Math.max(1, width - 2));
|
|
350
|
+
const styledTitle = this.colors.bold(this.style('brand', safeTitle));
|
|
351
|
+
const topPrefix = `${TERMINAL_GLYPHS.horizontal} ${styledTitle} `;
|
|
352
|
+
const top = `${TERMINAL_GLYPHS.topLeft}${topPrefix}${TERMINAL_GLYPHS.horizontal.repeat(Math.max(0, width + 2 - visibleLength(topPrefix)))}${TERMINAL_GLYPHS.topRight}`;
|
|
127
353
|
return [
|
|
128
354
|
top,
|
|
129
|
-
...
|
|
130
|
-
|
|
355
|
+
...wrapped.map((line) => `${TERMINAL_GLYPHS.vertical} ${padVisible(line, width)} ${TERMINAL_GLYPHS.vertical}`),
|
|
356
|
+
`${TERMINAL_GLYPHS.bottomLeft}${TERMINAL_GLYPHS.horizontal.repeat(width + 2)}${TERMINAL_GLYPHS.bottomRight}`,
|
|
131
357
|
''
|
|
132
358
|
].join('\n');
|
|
133
359
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
360
|
+
section(title, lines) {
|
|
361
|
+
return this.panel(title, lines);
|
|
362
|
+
}
|
|
363
|
+
definitionList(title, items) {
|
|
364
|
+
const labelWidth = Math.min(24, Math.max(0, ...items.map((item) => visibleLength(item.label))));
|
|
365
|
+
const lines = items.map((item) => {
|
|
366
|
+
return `${padVisible(item.label, labelWidth)} ${item.value}`;
|
|
367
|
+
});
|
|
368
|
+
if (this.layout === 'plain') {
|
|
369
|
+
return `${this.heading(title)}${items.map((item) => `${item.label}: ${item.value}\n`).join('')}\n`;
|
|
137
370
|
}
|
|
138
|
-
|
|
371
|
+
return this.panel(title, lines);
|
|
372
|
+
}
|
|
373
|
+
bulletList(title, items) {
|
|
374
|
+
const marker = this.layout === 'full' ? TERMINAL_GLYPHS.bullet : '-';
|
|
375
|
+
return this.panel(title, items.map((item) => `${marker} ${item}`));
|
|
376
|
+
}
|
|
377
|
+
choiceList(title, choices) {
|
|
378
|
+
const lines = choices.map((choice, index) => {
|
|
379
|
+
const marker = choice.selected
|
|
380
|
+
? TERMINAL_GLYPHS.selected
|
|
381
|
+
: this.layout === 'full'
|
|
382
|
+
? TERMINAL_GLYPHS.unselected
|
|
383
|
+
: `${index + 1}.`;
|
|
384
|
+
const number = this.layout === 'full' ? `${index + 1}.` : '';
|
|
385
|
+
const value = choice.value && choice.value !== choice.label
|
|
386
|
+
? this.style('metadata', ` (${choice.value})`)
|
|
387
|
+
: '';
|
|
388
|
+
const state = [
|
|
389
|
+
choice.default ? this.style('info', 'default') : '',
|
|
390
|
+
choice.disabled ? this.style('warning', 'unavailable') : ''
|
|
391
|
+
].filter(Boolean).join(', ');
|
|
392
|
+
return `${marker} ${number ? `${number} ` : ''}${choice.label}${value}${state ? ` [${state}]` : ''}`;
|
|
393
|
+
});
|
|
394
|
+
return this.panel(title, lines);
|
|
395
|
+
}
|
|
396
|
+
table(headers, rows) {
|
|
397
|
+
if (this.jsonMode || headers.length === 0) {
|
|
139
398
|
return '';
|
|
140
399
|
}
|
|
141
|
-
|
|
142
|
-
|
|
400
|
+
const normalizedHeaders = headers.map((header) => this.normalize(header));
|
|
401
|
+
const normalizedRows = rows.map((row) => row.map((cell) => this.normalize(cell)));
|
|
402
|
+
if (this.layout === 'plain') {
|
|
403
|
+
return normalizedRows.map((row) => row.map((cell, index) => `${normalizedHeaders[index] ?? `Column ${index + 1}`}: ${cell}`).join(' | ')).join('\n') + (rows.length > 0 ? '\n' : '');
|
|
143
404
|
}
|
|
144
|
-
const natural =
|
|
145
|
-
const
|
|
146
|
-
const available =
|
|
147
|
-
|
|
148
|
-
const
|
|
405
|
+
const natural = normalizedHeaders.map((header, index) => Math.max(visibleLength(header), ...normalizedRows.map((row) => visibleLength(row[index] ?? ''))));
|
|
406
|
+
const separator = ' ';
|
|
407
|
+
const available = (this.layout === 'full' ? this.contentWidth() : this.columns) -
|
|
408
|
+
separator.length * (headers.length - 1);
|
|
409
|
+
const widths = fitColumnWidths(natural, Math.max(headers.length * 4, available));
|
|
410
|
+
const renderPhysicalRow = (row) => {
|
|
411
|
+
const wrapped = widths.map((width, index) => wrapVisible(row[index] ?? '', width, true));
|
|
412
|
+
const height = Math.max(...wrapped.map((cell) => cell.length));
|
|
413
|
+
return Array.from({ length: height }, (_, lineIndex) => wrapped.map((cell, index) => padVisible(cell[lineIndex] ?? '', widths[index])).join(separator).trimEnd());
|
|
414
|
+
};
|
|
415
|
+
const header = renderPhysicalRow(normalizedHeaders).map((line) => this.colors.bold(line));
|
|
416
|
+
const separatorLine = widths.map((width) => TERMINAL_GLYPHS.horizontal.repeat(width)).join(separator);
|
|
149
417
|
return [
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
...
|
|
418
|
+
...header,
|
|
419
|
+
this.style('metadata', separatorLine),
|
|
420
|
+
...normalizedRows.flatMap(renderPhysicalRow),
|
|
153
421
|
''
|
|
154
422
|
].join('\n');
|
|
155
423
|
}
|
|
424
|
+
tableSection(title, headers, rows) {
|
|
425
|
+
const lines = this.table(headers, rows).trimEnd().split('\n');
|
|
426
|
+
return this.layout === 'full'
|
|
427
|
+
? this.panel(title, lines)
|
|
428
|
+
: `${this.heading(title)}${lines.join('\n')}\n\n`;
|
|
429
|
+
}
|
|
156
430
|
status(kind, label, detail) {
|
|
157
431
|
if (this.jsonMode) {
|
|
158
432
|
return '';
|
|
159
433
|
}
|
|
160
|
-
const
|
|
161
|
-
success:
|
|
162
|
-
info:
|
|
163
|
-
warning:
|
|
164
|
-
error:
|
|
165
|
-
pending:
|
|
434
|
+
const plainTokens = {
|
|
435
|
+
success: '[ok]',
|
|
436
|
+
info: '[info]',
|
|
437
|
+
warning: '[warn]',
|
|
438
|
+
error: '[error]',
|
|
439
|
+
pending: '[....]'
|
|
166
440
|
};
|
|
167
|
-
|
|
441
|
+
const richTokens = {
|
|
442
|
+
success: '✓',
|
|
443
|
+
info: 'i',
|
|
444
|
+
warning: '!',
|
|
445
|
+
error: '×',
|
|
446
|
+
pending: '…'
|
|
447
|
+
};
|
|
448
|
+
const token = this.layout === 'plain' ? plainTokens[kind] : richTokens[kind];
|
|
449
|
+
return `${this.style(kind === 'pending' ? 'pending' : kind, token)} ${this.colors.bold(label)}${detail ? `: ${detail}` : ''}\n`;
|
|
168
450
|
}
|
|
169
451
|
command(value) {
|
|
170
|
-
return this.jsonMode
|
|
452
|
+
return this.jsonMode
|
|
453
|
+
? ''
|
|
454
|
+
: `${this.style('command', '$')} ${this.colors.bold(value)}\n`;
|
|
455
|
+
}
|
|
456
|
+
prompt(label, defaultValue) {
|
|
457
|
+
if (this.jsonMode) {
|
|
458
|
+
return '';
|
|
459
|
+
}
|
|
460
|
+
const suffix = defaultValue ? ` [${defaultValue}]` : '';
|
|
461
|
+
return `${this.style('warning', '?')} ${this.colors.bold(label)}${this.style('metadata', suffix)}: `;
|
|
462
|
+
}
|
|
463
|
+
promptContext(label, detail) {
|
|
464
|
+
return this.panel(label, detail ? [detail] : []);
|
|
465
|
+
}
|
|
466
|
+
remedy(value) {
|
|
467
|
+
if (this.jsonMode) {
|
|
468
|
+
return '';
|
|
469
|
+
}
|
|
470
|
+
return `${this.style('info', 'Remedy:')} ${value}\n`;
|
|
471
|
+
}
|
|
472
|
+
compactError(message, remedy) {
|
|
473
|
+
if (this.jsonMode) {
|
|
474
|
+
return '';
|
|
475
|
+
}
|
|
476
|
+
const lines = message.split(/\r?\n/).filter(Boolean);
|
|
477
|
+
if (this.layout === 'full') {
|
|
478
|
+
return this.panel('Error', [
|
|
479
|
+
...lines.map((line) => this.style('error', line)),
|
|
480
|
+
...(remedy ? [`${this.style('info', 'Remedy:')} ${remedy}`] : [])
|
|
481
|
+
]);
|
|
482
|
+
}
|
|
483
|
+
return [
|
|
484
|
+
this.status('error', 'Error', lines[0] ?? message).trimEnd(),
|
|
485
|
+
...lines.slice(1).map((line) => ` ${line}`),
|
|
486
|
+
...(remedy ? [this.remedy(remedy).trimEnd()] : []),
|
|
487
|
+
''
|
|
488
|
+
].join('\n');
|
|
171
489
|
}
|
|
172
490
|
warning(value) {
|
|
173
491
|
return this.status('warning', 'Warning', value);
|
|
174
492
|
}
|
|
175
|
-
error(value) {
|
|
176
|
-
return this.
|
|
493
|
+
error(value, remedy) {
|
|
494
|
+
return this.compactError(value, remedy);
|
|
495
|
+
}
|
|
496
|
+
cancellation(value) {
|
|
497
|
+
return this.status('info', 'Cancelled', value);
|
|
498
|
+
}
|
|
499
|
+
completion(label, detail, items = [], nextCommand) {
|
|
500
|
+
return [
|
|
501
|
+
this.status('success', label, detail),
|
|
502
|
+
...(items.length > 0 ? [this.definitionList('Completion', items)] : []),
|
|
503
|
+
...(nextCommand ? [this.command(nextCommand)] : [])
|
|
504
|
+
].join('');
|
|
177
505
|
}
|
|
178
506
|
confirmation(value) {
|
|
179
|
-
return this.jsonMode ? '' : `${this.
|
|
507
|
+
return this.jsonMode ? '' : `${this.style('warning', '?')} ${this.colors.bold(value)}\n`;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
export class PresentationSession {
|
|
511
|
+
stdout;
|
|
512
|
+
stderr;
|
|
513
|
+
stdoutStream;
|
|
514
|
+
stderrStream;
|
|
515
|
+
constructor(options) {
|
|
516
|
+
this.stdoutStream = options.stdout;
|
|
517
|
+
this.stderrStream = options.stderr;
|
|
518
|
+
const rendererOptions = {
|
|
519
|
+
columns: options.columns,
|
|
520
|
+
color: options.color,
|
|
521
|
+
snapshot: options.snapshot,
|
|
522
|
+
json: options.json,
|
|
523
|
+
env: options.env,
|
|
524
|
+
layout: options.layout,
|
|
525
|
+
normalize: options.normalize
|
|
526
|
+
};
|
|
527
|
+
this.stdout = new TerminalRenderer({ stream: options.stdout, ...rendererOptions });
|
|
528
|
+
this.stderr = new TerminalRenderer({ stream: options.stderr, ...rendererOptions });
|
|
529
|
+
}
|
|
530
|
+
identity(subtitle) {
|
|
531
|
+
this.stdout.write(this.stdout.banner(subtitle));
|
|
532
|
+
}
|
|
533
|
+
commandIdentity(command, description) {
|
|
534
|
+
this.stdout.write(this.stdout.commandIdentity(command, description));
|
|
535
|
+
}
|
|
536
|
+
stage(title, detail) {
|
|
537
|
+
this.stdout.write(this.stdout.stage(title, detail));
|
|
538
|
+
}
|
|
539
|
+
section(title, lines) {
|
|
540
|
+
this.stdout.write(this.stdout.section(title, lines));
|
|
541
|
+
}
|
|
542
|
+
definitions(title, items) {
|
|
543
|
+
this.stdout.write(this.stdout.definitionList(title, items));
|
|
544
|
+
}
|
|
545
|
+
bullets(title, items) {
|
|
546
|
+
this.stdout.write(this.stdout.bulletList(title, items));
|
|
547
|
+
}
|
|
548
|
+
choices(title, choices) {
|
|
549
|
+
this.stdout.write(this.stdout.choiceList(title, choices));
|
|
550
|
+
}
|
|
551
|
+
prompt(label, defaultValue) {
|
|
552
|
+
this.stdout.write(this.stdout.prompt(label, defaultValue));
|
|
553
|
+
}
|
|
554
|
+
table(title, headers, rows) {
|
|
555
|
+
this.stdout.write(this.stdout.tableSection(title, headers, rows));
|
|
556
|
+
}
|
|
557
|
+
status(kind, label, detail) {
|
|
558
|
+
this.stdout.write(this.stdout.status(kind, label, detail));
|
|
559
|
+
}
|
|
560
|
+
warning(value) {
|
|
561
|
+
this.stdout.write(this.stdout.warning(value));
|
|
562
|
+
}
|
|
563
|
+
command(value) {
|
|
564
|
+
this.stdout.write(this.stdout.command(value));
|
|
565
|
+
}
|
|
566
|
+
remedy(value) {
|
|
567
|
+
this.stdout.write(this.stdout.remedy(value));
|
|
568
|
+
}
|
|
569
|
+
cancellation(value) {
|
|
570
|
+
this.stdout.write(this.stdout.cancellation(value));
|
|
571
|
+
}
|
|
572
|
+
completion(label, detail, items = [], nextCommand) {
|
|
573
|
+
this.stdout.write(this.stdout.completion(label, detail, items, nextCommand));
|
|
574
|
+
}
|
|
575
|
+
error(message, remedy) {
|
|
576
|
+
if (this.stderr.jsonMode) {
|
|
577
|
+
this.stderrStream.write([
|
|
578
|
+
message.trimEnd(),
|
|
579
|
+
...(remedy ? [`Remedy: ${remedy}`] : [])
|
|
580
|
+
].join('\n') + '\n');
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
this.stderr.write(this.stderr.compactError(message, remedy));
|
|
584
|
+
}
|
|
585
|
+
rawStdout(value) {
|
|
586
|
+
this.stdoutStream.write(value);
|
|
587
|
+
}
|
|
588
|
+
rawStderr(value) {
|
|
589
|
+
this.stderrStream.write(value);
|
|
590
|
+
}
|
|
591
|
+
childStreams() {
|
|
592
|
+
return {
|
|
593
|
+
stdout: this.stdoutStream,
|
|
594
|
+
stderr: this.stderrStream
|
|
595
|
+
};
|
|
180
596
|
}
|
|
181
597
|
}
|
|
182
598
|
//# sourceMappingURL=terminal.js.map
|