@fecommunity/reactpress 4.0.0-beta.2 → 4.0.0-beta.3
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/out/lib/cli-events.d.ts +12 -0
- package/out/lib/cli-events.d.ts.map +1 -0
- package/out/lib/cli-events.js +54 -0
- package/out/lib/cli-events.js.map +1 -0
- package/out/lib/command-palette.d.ts +6 -0
- package/out/lib/command-palette.d.ts.map +1 -0
- package/out/lib/command-palette.js +53 -0
- package/out/lib/command-palette.js.map +1 -0
- package/out/lib/context-status.d.ts +2 -0
- package/out/lib/context-status.d.ts.map +1 -0
- package/out/lib/context-status.js +148 -0
- package/out/lib/context-status.js.map +1 -0
- package/out/lib/design-tokens.d.ts +38 -0
- package/out/lib/design-tokens.d.ts.map +1 -0
- package/out/lib/design-tokens.js +55 -0
- package/out/lib/design-tokens.js.map +1 -0
- package/out/lib/dev-banner.js +10 -5
- package/out/lib/dev-banner.js.map +1 -1
- package/out/lib/dev.js +20 -1
- package/out/lib/dev.js.map +1 -1
- package/out/lib/i18n/index.js +5 -1
- package/out/lib/i18n/index.js.map +1 -1
- package/out/lib/i18n/strings.js +99 -41
- package/out/lib/i18n/strings.js.map +1 -1
- package/out/lib/menu-actions.d.ts +2 -0
- package/out/lib/menu-actions.d.ts.map +1 -0
- package/out/lib/menu-actions.js +418 -0
- package/out/lib/menu-actions.js.map +1 -0
- package/out/lib/nginx.js +5 -2
- package/out/lib/nginx.js.map +1 -1
- package/out/lib/remote-dev.js +5 -1
- package/out/lib/remote-dev.js.map +1 -1
- package/out/lib/shell-mode.d.ts +5 -0
- package/out/lib/shell-mode.d.ts.map +1 -0
- package/out/lib/shell-mode.js +23 -0
- package/out/lib/shell-mode.js.map +1 -0
- package/out/lib/theme-warmup.js +1 -1
- package/out/lib/theme-warmup.js.map +1 -1
- package/out/shell/app.d.ts +12 -0
- package/out/shell/app.d.ts.map +1 -0
- package/out/shell/app.js +384 -0
- package/out/shell/app.js.map +1 -0
- package/out/shell/components/brand-banner.d.ts +20 -0
- package/out/shell/components/brand-banner.d.ts.map +1 -0
- package/out/shell/components/brand-banner.js +93 -0
- package/out/shell/components/brand-banner.js.map +1 -0
- package/out/shell/components/command-palette.d.ts +10 -0
- package/out/shell/components/command-palette.d.ts.map +1 -0
- package/out/shell/components/command-palette.js +77 -0
- package/out/shell/components/command-palette.js.map +1 -0
- package/out/shell/components/dev-stepper.d.ts +8 -0
- package/out/shell/components/dev-stepper.d.ts.map +1 -0
- package/out/shell/components/dev-stepper.js +54 -0
- package/out/shell/components/dev-stepper.js.map +1 -0
- package/out/shell/components/ink-theme.d.ts +34 -0
- package/out/shell/components/ink-theme.d.ts.map +1 -0
- package/out/shell/components/ink-theme.js +86 -0
- package/out/shell/components/ink-theme.js.map +1 -0
- package/out/shell/components/menu-list.d.ts +7 -0
- package/out/shell/components/menu-list.d.ts.map +1 -0
- package/out/shell/components/menu-list.js +49 -0
- package/out/shell/components/menu-list.js.map +1 -0
- package/out/shell/components/pulse-bar.d.ts +6 -0
- package/out/shell/components/pulse-bar.d.ts.map +1 -0
- package/out/shell/components/pulse-bar.js +55 -0
- package/out/shell/components/pulse-bar.js.map +1 -0
- package/out/shell/components/status-rack.d.ts +16 -0
- package/out/shell/components/status-rack.d.ts.map +1 -0
- package/out/shell/components/status-rack.js +47 -0
- package/out/shell/components/status-rack.js.map +1 -0
- package/out/shell/hooks/use-terminal-size.d.ts +2 -0
- package/out/shell/hooks/use-terminal-size.d.ts.map +1 -0
- package/out/shell/hooks/use-terminal-size.js +27 -0
- package/out/shell/hooks/use-terminal-size.js.map +1 -0
- package/out/shell/ink-shell.mjs +12056 -0
- package/out/shell/lib/shell-layout.d.ts +27 -0
- package/out/shell/lib/shell-layout.d.ts.map +1 -0
- package/out/shell/lib/shell-layout.js +60 -0
- package/out/shell/lib/shell-layout.js.map +1 -0
- package/out/shell/runtime/render-ink.d.ts +2 -0
- package/out/shell/runtime/render-ink.d.ts.map +1 -0
- package/out/shell/runtime/render-ink.js +89 -0
- package/out/shell/runtime/render-ink.js.map +1 -0
- package/out/ui/banner-startup.d.ts +2 -0
- package/out/ui/banner-startup.d.ts.map +1 -0
- package/out/ui/banner-startup.js +125 -0
- package/out/ui/banner-startup.js.map +1 -0
- package/out/ui/banner.js +402 -288
- package/out/ui/banner.js.map +1 -1
- package/out/ui/interactive.js +149 -125
- package/out/ui/interactive.js.map +1 -1
- package/out/ui/startup-splash.d.ts +2 -0
- package/out/ui/startup-splash.d.ts.map +1 -0
- package/out/ui/startup-splash.js +185 -0
- package/out/ui/startup-splash.js.map +1 -0
- package/out/ui/theme.js +63 -10
- package/out/ui/theme.js.map +1 -1
- package/package.json +1 -1
package/out/ui/banner.js
CHANGED
|
@@ -3,14 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
// @ts-nocheck
|
|
4
4
|
const os = require('os');
|
|
5
5
|
const chalk = require('chalk');
|
|
6
|
-
const { brand, icon, palette, visibleLength, padRight,
|
|
6
|
+
const { brand, icon, palette, visibleLength, padRight, gradientText, macTrafficLights, systemStatusBadge, statusPill, pulseBar, } = require('./theme');
|
|
7
7
|
const { t } = require('../lib/i18n');
|
|
8
8
|
const { getCliVersion } = require('../lib/paths');
|
|
9
|
-
/**
|
|
10
|
-
* "REACTPRESS" rendered in the ANSI Shadow font.
|
|
11
|
-
* Each row is exactly 81 single-cell columns, so we can size the surrounding
|
|
12
|
-
* cyber-card deterministically without measuring per-glyph widths.
|
|
13
|
-
*/
|
|
9
|
+
/** Full ANSI Shadow logo — 81 cells wide. */
|
|
14
10
|
const TECH_LOGO = [
|
|
15
11
|
'██████╗ ███████╗ █████╗ ██████╗████████╗██████╗ ██████╗ ███████╗███████╗███████╗',
|
|
16
12
|
'██╔══██╗██╔════╝██╔══██╗██╔════╝╚══██╔══╝██╔══██╗██╔══██╗██╔════╝██╔════╝██╔════╝',
|
|
@@ -19,7 +15,7 @@ const TECH_LOGO = [
|
|
|
19
15
|
'██║ ██║███████╗██║ ██║╚██████╗ ██║ ██║ ██║ ██║███████╗███████║███████║',
|
|
20
16
|
'╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝',
|
|
21
17
|
];
|
|
22
|
-
const LOGO_WIDTH =
|
|
18
|
+
const LOGO_WIDTH = TECH_LOGO[0].length;
|
|
23
19
|
const LOGO_GRADIENTS = [
|
|
24
20
|
[palette.pink, palette.primary],
|
|
25
21
|
[palette.pink, palette.primary],
|
|
@@ -29,19 +25,9 @@ const LOGO_GRADIENTS = [
|
|
|
29
25
|
[palette.accent, palette.primary],
|
|
30
26
|
];
|
|
31
27
|
const REPO_URL = 'https://github.com/fecommunity/reactpress';
|
|
32
|
-
/**
|
|
33
|
-
* Shorter, human-friendly form of REPO_URL shown beneath the title bar.
|
|
34
|
-
* The clickable hyperlink still resolves to the full https:// URL via
|
|
35
|
-
* `hyperlink()`, so users can `cmd+click` from any modern terminal.
|
|
36
|
-
*/
|
|
37
28
|
const REPO_DISPLAY = 'github.com/fecommunity/reactpress';
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
* Warp, WezTerm, modern macOS Terminal, VS Code, GNOME Terminal, Kitty, …)
|
|
41
|
-
* render the label as a clickable link. We only emit the escape sequence when
|
|
42
|
-
* stdout is a real TTY — otherwise (CI logs, file redirects, dumb terminals)
|
|
43
|
-
* we fall back to the plain styled label so users never see the raw `]8;;`.
|
|
44
|
-
*/
|
|
29
|
+
const CARD_MIN = 68;
|
|
30
|
+
const ASCII_CARD_PAD = 8;
|
|
45
31
|
function hyperlink(url, label) {
|
|
46
32
|
if (!process.stdout.isTTY)
|
|
47
33
|
return label;
|
|
@@ -53,322 +39,450 @@ function homify(p) {
|
|
|
53
39
|
if (!p)
|
|
54
40
|
return p;
|
|
55
41
|
const home = os.homedir();
|
|
56
|
-
if (home && p.startsWith(home))
|
|
42
|
+
if (home && p.startsWith(home))
|
|
57
43
|
return '~' + p.slice(home.length);
|
|
58
|
-
}
|
|
59
44
|
return p;
|
|
60
45
|
}
|
|
46
|
+
function truncateText(text, maxLen) {
|
|
47
|
+
const raw = String(text);
|
|
48
|
+
if (visibleLength(raw) <= maxLen)
|
|
49
|
+
return raw;
|
|
50
|
+
if (maxLen <= 3)
|
|
51
|
+
return '…';
|
|
52
|
+
let out = '…';
|
|
53
|
+
for (let i = raw.length - 1; i >= 0; i -= 1) {
|
|
54
|
+
const candidate = '…' + raw.slice(i);
|
|
55
|
+
if (visibleLength(candidate) <= maxLen) {
|
|
56
|
+
out = candidate;
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
function bannerColumns() {
|
|
63
|
+
const raw = Number(process.stdout.columns);
|
|
64
|
+
return raw >= 48 ? raw : 80;
|
|
65
|
+
}
|
|
66
|
+
/** Core dev stack — Docker/Nginx are shown but do not affect the SYSTEM badge. */
|
|
67
|
+
const CORE_SYSTEM_SERVICES = new Set(['sqlite', 'mysql', 'server', 'web']);
|
|
68
|
+
function deriveSystemState(project, status) {
|
|
69
|
+
const type = project && project.type;
|
|
70
|
+
if (type !== 'monorepo' && type !== 'standalone') {
|
|
71
|
+
return 'pending';
|
|
72
|
+
}
|
|
73
|
+
if (!status || !Array.isArray(status.components) || status.components.length === 0) {
|
|
74
|
+
return 'pending';
|
|
75
|
+
}
|
|
76
|
+
const core = status.components.filter((c) => CORE_SYSTEM_SERVICES.has(c.id));
|
|
77
|
+
if (core.length === 0)
|
|
78
|
+
return 'pending';
|
|
79
|
+
const okCount = core.filter((c) => c.ok).length;
|
|
80
|
+
if (okCount === core.length)
|
|
81
|
+
return 'online';
|
|
82
|
+
if (okCount === 0)
|
|
83
|
+
return 'error';
|
|
84
|
+
return 'partial';
|
|
85
|
+
}
|
|
86
|
+
function resolveSystemState(options) {
|
|
87
|
+
if (options && options.systemState)
|
|
88
|
+
return options.systemState;
|
|
89
|
+
if (options && options.status) {
|
|
90
|
+
return deriveSystemState(options.project, options.status);
|
|
91
|
+
}
|
|
92
|
+
const type = options && options.project && options.project.type;
|
|
93
|
+
if (type === 'monorepo' || type === 'standalone')
|
|
94
|
+
return 'pending';
|
|
95
|
+
return 'pending';
|
|
96
|
+
}
|
|
97
|
+
function componentLabel(component) {
|
|
98
|
+
return t(`banner.service.${component.id}`).trim();
|
|
99
|
+
}
|
|
100
|
+
function computeLabelWidth(components) {
|
|
101
|
+
const labels = [
|
|
102
|
+
t('banner.label.mode').trim(),
|
|
103
|
+
t('banner.label.path').trim(),
|
|
104
|
+
...(components || []).map((c) => componentLabel(c)),
|
|
105
|
+
];
|
|
106
|
+
return Math.max(...labels.map((label) => visibleLength(label)), 4);
|
|
107
|
+
}
|
|
108
|
+
function componentStatusPill(component) {
|
|
109
|
+
if (component.ok === 'pending') {
|
|
110
|
+
return statusPill('pending', { pending: t('banner.pulsePending') });
|
|
111
|
+
}
|
|
112
|
+
if (component.id === 'sqlite' || component.id === 'mysql') {
|
|
113
|
+
return statusPill(component.ok, {
|
|
114
|
+
on: t('menu.statusReady'),
|
|
115
|
+
off: t('menu.statusNotReady'),
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
if (component.id === 'docker') {
|
|
119
|
+
return statusPill(component.ok, {
|
|
120
|
+
on: t('menu.statusYes'),
|
|
121
|
+
off: t('menu.statusNo'),
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
if (component.id === 'nginx') {
|
|
125
|
+
return statusPill(component.ok, {
|
|
126
|
+
on: t('banner.nginxRunning'),
|
|
127
|
+
off: t('banner.nginxStopped'),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
return statusPill(component.ok, {
|
|
131
|
+
on: t('menu.statusOn'),
|
|
132
|
+
off: t('menu.statusOff'),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
function appendComponentStatusRows(lines, innerWidth, status, lw) {
|
|
136
|
+
if (!status || !Array.isArray(status.components))
|
|
137
|
+
return;
|
|
138
|
+
for (const component of status.components) {
|
|
139
|
+
lines.push(cardRow(infoRow(componentLabel(component), componentStatusPill(component), lw), innerWidth));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function resolveLayout(cols) {
|
|
143
|
+
const showAscii = cols >= LOGO_WIDTH + ASCII_CARD_PAD;
|
|
144
|
+
const width = showAscii
|
|
145
|
+
? Math.min(cols - 2, Math.max(LOGO_WIDTH + ASCII_CARD_PAD, 88))
|
|
146
|
+
: Math.min(Math.max(CARD_MIN, cols - 4), 88);
|
|
147
|
+
return { showAscii, width };
|
|
148
|
+
}
|
|
149
|
+
function centerLine(content, innerWidth) {
|
|
150
|
+
const pad = Math.max(0, Math.floor((innerWidth - visibleLength(content)) / 2));
|
|
151
|
+
return ' '.repeat(pad) + content;
|
|
152
|
+
}
|
|
61
153
|
function renderLogoLines() {
|
|
62
154
|
return TECH_LOGO.map((line, i) => gradientText(line, LOGO_GRADIENTS[i]));
|
|
63
155
|
}
|
|
156
|
+
function mixHexLocal(a, b, t) {
|
|
157
|
+
const parse = (h) => {
|
|
158
|
+
const s = String(h || '#000000').replace('#', '');
|
|
159
|
+
return {
|
|
160
|
+
r: parseInt(s.substring(0, 2), 16),
|
|
161
|
+
g: parseInt(s.substring(2, 4), 16),
|
|
162
|
+
b: parseInt(s.substring(4, 6), 16),
|
|
163
|
+
};
|
|
164
|
+
};
|
|
165
|
+
const pa = parse(a);
|
|
166
|
+
const pb = parse(b);
|
|
167
|
+
const r = Math.round(pa.r + (pb.r - pa.r) * t);
|
|
168
|
+
const g = Math.round(pa.g + (pb.g - pa.g) * t);
|
|
169
|
+
const bl = Math.round(pa.b + (pb.b - pa.b) * t);
|
|
170
|
+
const pad = (n) => n.toString(16).padStart(2, '0');
|
|
171
|
+
return `#${pad(r)}${pad(g)}${pad(bl)}`;
|
|
172
|
+
}
|
|
173
|
+
/** Animated logo — horizontal light streak sweeps across ASCII art. */
|
|
174
|
+
function renderAnimatedLogoLines(frame = 0, revealRatio = 1) {
|
|
175
|
+
const beamSpeed = 3.6;
|
|
176
|
+
const beamGlow = 16;
|
|
177
|
+
const cycleLen = LOGO_WIDTH + beamGlow * 2;
|
|
178
|
+
const head = (frame * beamSpeed) % cycleLen;
|
|
179
|
+
const trail = (head - beamGlow * 0.75 + cycleLen) % cycleLen;
|
|
180
|
+
const streamHot = '#FFFFFF';
|
|
181
|
+
const streamCore = palette.accent;
|
|
182
|
+
const streamTrail = palette.pink;
|
|
183
|
+
function beamIntensity(col, rowIndex, beamHead) {
|
|
184
|
+
const pos = col + rowIndex * 2.2;
|
|
185
|
+
const raw = Math.abs(pos - beamHead);
|
|
186
|
+
const wrapped = Math.min(raw, cycleLen - raw);
|
|
187
|
+
if (wrapped >= beamGlow)
|
|
188
|
+
return 0;
|
|
189
|
+
const t = 1 - wrapped / beamGlow;
|
|
190
|
+
return t * t;
|
|
191
|
+
}
|
|
192
|
+
function paintChar(ch, ci, rowIndex, colors, lineLen) {
|
|
193
|
+
if (ch === ' ')
|
|
194
|
+
return ch;
|
|
195
|
+
const n = Math.max(lineLen - 1, 1);
|
|
196
|
+
const ratio = ci / n;
|
|
197
|
+
const base = mixHexLocal(colors[0], colors[1] || colors[0], ratio);
|
|
198
|
+
const dimBase = mixHexLocal(base, palette.surface, 0.85);
|
|
199
|
+
const primary = beamIntensity(ci, rowIndex, head);
|
|
200
|
+
const secondary = beamIntensity(ci, rowIndex, trail) * 0.55;
|
|
201
|
+
const glow = Math.min(1, primary + secondary);
|
|
202
|
+
if (glow <= 0.03) {
|
|
203
|
+
return chalk.hex(dimBase)(ch);
|
|
204
|
+
}
|
|
205
|
+
let lit = dimBase;
|
|
206
|
+
if (primary > 0.65) {
|
|
207
|
+
lit = mixHexLocal(streamHot, streamCore, 1 - primary);
|
|
208
|
+
}
|
|
209
|
+
else if (glow > 0.3) {
|
|
210
|
+
lit = mixHexLocal(mixHexLocal(dimBase, streamTrail, 0.15), streamCore, glow);
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
lit = mixHexLocal(dimBase, streamTrail, glow * 0.9);
|
|
214
|
+
}
|
|
215
|
+
const c = chalk.hex(lit);
|
|
216
|
+
return glow > 0.45 ? c.bold(ch) : c(ch);
|
|
217
|
+
}
|
|
218
|
+
return TECH_LOGO.map((line, rowIndex) => {
|
|
219
|
+
const rowThreshold = (rowIndex + 1) / TECH_LOGO.length;
|
|
220
|
+
if (revealRatio < rowThreshold - 0.06) {
|
|
221
|
+
const ghost = mixHexLocal(palette.border, palette.surface, 0.5);
|
|
222
|
+
return chalk.hex(ghost)([...line]
|
|
223
|
+
.map((ch) => (ch === ' ' ? ch : '░'))
|
|
224
|
+
.join(''));
|
|
225
|
+
}
|
|
226
|
+
const colors = LOGO_GRADIENTS[rowIndex];
|
|
227
|
+
return [...line]
|
|
228
|
+
.map((ch, ci) => paintChar(ch, ci, rowIndex, colors, line.length))
|
|
229
|
+
.join('');
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
function startupPulseRow(innerWidth, { completed = 0, total = 0, ready = false, frame = 0 } = {}) {
|
|
233
|
+
const barWidth = Math.min(24, Math.max(8, innerWidth - 22));
|
|
234
|
+
let filled = 0;
|
|
235
|
+
if (total > 0) {
|
|
236
|
+
filled = Math.round((completed / total) * barWidth);
|
|
237
|
+
if (!ready)
|
|
238
|
+
filled = Math.min(barWidth, filled + (frame % 2));
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
filled = 4 + (frame % Math.max(1, barWidth - 4));
|
|
242
|
+
}
|
|
243
|
+
const label = brand.muted(padRight(t('banner.pulseLabel').trim(), 6));
|
|
244
|
+
const bar = pulseBar(barWidth, filled);
|
|
245
|
+
const status = ready
|
|
246
|
+
? brand.success(t('banner.pulseReady'))
|
|
247
|
+
: brand.warn(t('banner.pulsePending'));
|
|
248
|
+
return centerLine(`${label}${bar} ${status}`, innerWidth);
|
|
249
|
+
}
|
|
250
|
+
function cardTop(width) {
|
|
251
|
+
return brand.border(` ╭${'─'.repeat(width - 2)}╮`);
|
|
252
|
+
}
|
|
253
|
+
function cardBottom(width) {
|
|
254
|
+
return brand.border(` ╰${'─'.repeat(width - 2)}╯`);
|
|
255
|
+
}
|
|
256
|
+
function cardRow(content, innerWidth) {
|
|
257
|
+
const padded = padRight(content, innerWidth);
|
|
258
|
+
return brand.border(' │ ') + padded + brand.border(' │');
|
|
259
|
+
}
|
|
260
|
+
function cardGap(innerWidth) {
|
|
261
|
+
return cardRow('', innerWidth);
|
|
262
|
+
}
|
|
263
|
+
function titleBarRow(innerWidth, version, systemState) {
|
|
264
|
+
const lights = macTrafficLights() + brand.dim(' ');
|
|
265
|
+
const lightsW = visibleLength(lights);
|
|
266
|
+
const title = brand.dim(`reactpress · v${version}`);
|
|
267
|
+
const titleW = visibleLength(title);
|
|
268
|
+
const status = systemStatusBadge(systemState);
|
|
269
|
+
const statusW = visibleLength(status);
|
|
270
|
+
const titleStart = Math.max(lightsW + 1, Math.floor((innerWidth - titleW) / 2));
|
|
271
|
+
const padBeforeTitle = Math.max(0, titleStart - lightsW);
|
|
272
|
+
let line = lights + ' '.repeat(padBeforeTitle) + title;
|
|
273
|
+
const gap = innerWidth - visibleLength(line) - statusW;
|
|
274
|
+
if (gap >= 2) {
|
|
275
|
+
line += ' '.repeat(gap) + status;
|
|
276
|
+
}
|
|
277
|
+
else if (gap >= 0) {
|
|
278
|
+
line += ' '.repeat(gap);
|
|
279
|
+
}
|
|
280
|
+
return padRight(line, innerWidth);
|
|
281
|
+
}
|
|
282
|
+
function repoRow(innerWidth) {
|
|
283
|
+
const link = brand.dim(icon.link + ' ') +
|
|
284
|
+
hyperlink(REPO_URL, brand.dim.underline(REPO_DISPLAY));
|
|
285
|
+
return centerLine(link, innerWidth);
|
|
286
|
+
}
|
|
287
|
+
function headerRows(innerWidth, version, systemState) {
|
|
288
|
+
return [titleBarRow(innerWidth, version, systemState), repoRow(innerWidth)];
|
|
289
|
+
}
|
|
290
|
+
function wordmarkHero() {
|
|
291
|
+
const bars = brand.border('═══');
|
|
292
|
+
const word = gradientText('REACTPRESS', [palette.pink, palette.primary, palette.accent], {
|
|
293
|
+
bold: true,
|
|
294
|
+
});
|
|
295
|
+
return `${bars} ${word} ${bars}`;
|
|
296
|
+
}
|
|
297
|
+
/** Compact wordmark with the same streaming-light sweep. */
|
|
298
|
+
function renderAnimatedWordmark(frame = 0) {
|
|
299
|
+
const text = 'REACTPRESS';
|
|
300
|
+
const beamSpeed = 3.2;
|
|
301
|
+
const beamGlow = 6;
|
|
302
|
+
const cycleLen = text.length + beamGlow * 2;
|
|
303
|
+
const head = (frame * beamSpeed) % cycleLen;
|
|
304
|
+
const streamHot = '#F0FDFF';
|
|
305
|
+
const bars = brand.border('═══');
|
|
306
|
+
const painted = [...text]
|
|
307
|
+
.map((ch, ci) => {
|
|
308
|
+
const raw = Math.abs(ci + 0.5 - head);
|
|
309
|
+
const wrapped = Math.min(raw, cycleLen - raw);
|
|
310
|
+
const glow = wrapped >= beamGlow ? 0 : Math.pow(1 - wrapped / beamGlow, 2);
|
|
311
|
+
const base = mixHexLocal(palette.pink, palette.accent, ci / Math.max(text.length - 1, 1));
|
|
312
|
+
const dimBase = mixHexLocal(base, palette.surface, 0.65);
|
|
313
|
+
if (glow <= 0.05)
|
|
314
|
+
return chalk.hex(dimBase).bold(ch);
|
|
315
|
+
const lit = glow > 0.75
|
|
316
|
+
? mixHexLocal(streamHot, palette.accent, 1 - glow)
|
|
317
|
+
: mixHexLocal(dimBase, palette.accent, glow);
|
|
318
|
+
const c = chalk.hex(lit);
|
|
319
|
+
return glow > 0.5 ? c.bold(ch) : c(ch);
|
|
320
|
+
})
|
|
321
|
+
.join('');
|
|
322
|
+
return `${bars} ${painted} ${bars}`;
|
|
323
|
+
}
|
|
324
|
+
function taglineRow() {
|
|
325
|
+
return `${brand.accent('◇')} ${brand.accent(t('banner.tagline').trim())}`;
|
|
326
|
+
}
|
|
327
|
+
function softRule(innerWidth) {
|
|
328
|
+
const w = Math.min(innerWidth - 4, Math.max(28, innerWidth - 16));
|
|
329
|
+
return centerLine(brand.border('─'.repeat(w)), innerWidth);
|
|
330
|
+
}
|
|
64
331
|
function modeChip(type) {
|
|
65
332
|
if (type === 'monorepo') {
|
|
66
|
-
return chalk
|
|
67
|
-
.bgHex(palette.primary)
|
|
68
|
-
.hex('#0B1220')
|
|
69
|
-
.bold(` ${t('banner.mode.monorepo')} `);
|
|
333
|
+
return chalk.bgHex(palette.primary).hex('#0B1220').bold(` ${t('banner.mode.monorepo').trim()} `);
|
|
70
334
|
}
|
|
71
335
|
if (type === 'standalone') {
|
|
72
|
-
return chalk
|
|
73
|
-
.bgHex(palette.accent)
|
|
74
|
-
.hex('#0B1220')
|
|
75
|
-
.bold(` ${t('banner.mode.standalone')} `);
|
|
336
|
+
return chalk.bgHex(palette.accent).hex('#0B1220').bold(` ${t('banner.mode.standalone').trim()} `);
|
|
76
337
|
}
|
|
77
|
-
return chalk
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Decide how "ready" the welcome banner should look. When a fully
|
|
84
|
-
* initialized project is detected we render the pulse bar at 100% and
|
|
85
|
-
* report `ONLINE` status, instead of the static 70% placeholder that used
|
|
86
|
-
* to make `doctor` runs look incomplete even when everything passed.
|
|
87
|
-
*/
|
|
88
|
-
function bannerReadyState(options) {
|
|
89
|
-
const type = options && options.project && options.project.type;
|
|
90
|
-
if (type === 'monorepo' || type === 'standalone') {
|
|
91
|
-
return { ratio: 1, ready: true };
|
|
92
|
-
}
|
|
93
|
-
return { ratio: 0.4, ready: false };
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Build the top edge of the cyber-card with a centered title block:
|
|
97
|
-
* ╔══════════[ REACTPRESS · v3.0.3 ]══════════╗
|
|
98
|
-
*/
|
|
99
|
-
function brandedTopBorder(version, width) {
|
|
100
|
-
const titleBlock = brand.primary('[') +
|
|
101
|
-
' ' +
|
|
102
|
-
gradientText('REACTPRESS', [palette.primary, palette.accent], { bold: true }) +
|
|
103
|
-
' ' +
|
|
104
|
-
brand.muted('·') +
|
|
105
|
-
' ' +
|
|
106
|
-
brand.accent(`v${version}`) +
|
|
107
|
-
' ' +
|
|
108
|
-
brand.primary(']');
|
|
109
|
-
const dashTotal = Math.max(0, width - 2 - visibleLength(titleBlock));
|
|
110
|
-
const left = Math.floor(dashTotal / 2);
|
|
111
|
-
const right = dashTotal - left;
|
|
112
|
-
return (brand.primary('╔' + '═'.repeat(left)) +
|
|
113
|
-
titleBlock +
|
|
114
|
-
brand.primary('═'.repeat(right) + '╗'));
|
|
115
|
-
}
|
|
116
|
-
function bottomBorder(width) {
|
|
117
|
-
return brand.primary('╚' + '═'.repeat(width - 2) + '╝');
|
|
118
|
-
}
|
|
119
|
-
function bodyLine(content, innerWidth) {
|
|
120
|
-
const padded = padRight(content, innerWidth);
|
|
121
|
-
return brand.primary('║ ') + padded + brand.primary(' ║');
|
|
122
|
-
}
|
|
123
|
-
function emptyBodyLine(innerWidth) {
|
|
124
|
-
return bodyLine('', innerWidth);
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* A subtle "CRT scan-line" rendered just under the logo.
|
|
128
|
-
* ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
|
|
129
|
-
*/
|
|
130
|
-
function scanline(width) {
|
|
131
|
-
return brand.muted('▔'.repeat(width));
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Width of the left-side banner label column.
|
|
135
|
-
*
|
|
136
|
-
* Sized to fit our longest English label (`MODE` / `PATH` → 4 cells)
|
|
137
|
-
* plus a 2-cell trailing gap, which also accommodates the Chinese
|
|
138
|
-
* translations `模式` / `路径` (4 East-Asian cells each).
|
|
139
|
-
*/
|
|
140
|
-
const LABEL_WIDTH = 6;
|
|
141
|
-
/**
|
|
142
|
-
* Centered, dim repo subtitle that sits directly under the top border.
|
|
143
|
-
* Replaces the previous in-body `◇ REPO ↗ …` row, which competed visually
|
|
144
|
-
* with the operational fields (MODE / PATH / pulse) further down.
|
|
145
|
-
*/
|
|
146
|
-
function repoSubline(innerWidth) {
|
|
147
|
-
const link = brand.muted('↗ ') + hyperlink(REPO_URL, brand.accent.underline(REPO_DISPLAY));
|
|
148
|
-
const pad = Math.max(0, Math.floor((innerWidth - visibleLength(link)) / 2));
|
|
149
|
-
return ' '.repeat(pad) + link;
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Single-cell-wide chip label, e.g. `◇ MODE ▸ monorepo`.
|
|
153
|
-
*/
|
|
154
|
-
function infoRow(label, value) {
|
|
155
|
-
return (brand.accent('◇ ') +
|
|
156
|
-
brand.muted(padRight(label, LABEL_WIDTH)) +
|
|
157
|
-
' ' +
|
|
158
|
-
brand.primary('▸ ') +
|
|
159
|
-
brand.dim(value));
|
|
160
|
-
}
|
|
161
|
-
/**
|
|
162
|
-
* Render the "command rail" navigation footer:
|
|
163
|
-
* ⟫ init ⟫ dev ⟫ build ⟫ deploy ⟫ publish
|
|
164
|
-
*/
|
|
338
|
+
return chalk.bgHex(palette.gray).hex('#0B1220').bold(` ${t('banner.mode.uninitialized').trim()} `);
|
|
339
|
+
}
|
|
340
|
+
function infoRow(label, value, lw) {
|
|
341
|
+
return brand.muted(padRight(label, lw)) + brand.border('│ ') + value;
|
|
342
|
+
}
|
|
165
343
|
function commandRail() {
|
|
166
344
|
const items = ['init', 'dev', 'build', 'deploy', 'publish'];
|
|
167
345
|
return items
|
|
168
|
-
.map((name) => brand.primary('
|
|
169
|
-
.join(brand.
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
* Wide, full-fat cyber banner: ASCII logo + scan-line + bordered card.
|
|
173
|
-
*/
|
|
174
|
-
function printWideBanner(version, options) {
|
|
175
|
-
const cols = terminalWidth();
|
|
176
|
-
const cardWidth = Math.min(Math.max(LOGO_WIDTH + 8, 88), cols - 2);
|
|
177
|
-
const innerWidth = cardWidth - 4;
|
|
178
|
-
const lines = [];
|
|
179
|
-
lines.push('');
|
|
180
|
-
lines.push(' ' + brandedTopBorder(version, cardWidth));
|
|
181
|
-
lines.push(' ' + bodyLine(repoSubline(innerWidth), innerWidth));
|
|
182
|
-
lines.push(' ' + emptyBodyLine(innerWidth));
|
|
183
|
-
const logoIndent = Math.max(0, Math.floor((innerWidth - LOGO_WIDTH) / 2));
|
|
184
|
-
const indent = ' '.repeat(logoIndent);
|
|
185
|
-
for (const logoLine of renderLogoLines()) {
|
|
186
|
-
lines.push(' ' + bodyLine(indent + logoLine, innerWidth));
|
|
187
|
-
}
|
|
188
|
-
const scanWidth = Math.min(innerWidth - 2, LOGO_WIDTH);
|
|
189
|
-
const scanIndent = ' '.repeat(Math.max(0, Math.floor((innerWidth - scanWidth) / 2)));
|
|
190
|
-
lines.push(' ' + bodyLine(scanIndent + scanline(scanWidth), innerWidth));
|
|
191
|
-
lines.push(' ' + emptyBodyLine(innerWidth));
|
|
192
|
-
const ready = bannerReadyState(options);
|
|
193
|
-
const subtitle = chalk.bold(brand.accent('◆ ')) +
|
|
194
|
-
gradientText(t('banner.subtitle').trim(), [palette.accent, palette.primary, palette.pink], {
|
|
195
|
-
bold: true,
|
|
196
|
-
});
|
|
197
|
-
const stateLabel = ready.ready
|
|
198
|
-
? brand.success(t('banner.systemOnline').trim())
|
|
199
|
-
: brand.warn(t('banner.systemPending').trim());
|
|
200
|
-
const right = statusLights(ready.ready ? 'online' : 'pending') +
|
|
201
|
-
' ' +
|
|
202
|
-
brand.dim(t('banner.systemLabel').trim() + ' ') +
|
|
203
|
-
stateLabel;
|
|
204
|
-
lines.push(' ' + bodyLine(subtitle + spacer(subtitle, right, innerWidth) + right, innerWidth));
|
|
205
|
-
lines.push(' ' + emptyBodyLine(innerWidth));
|
|
346
|
+
.map((name) => brand.primary('》 ') + gradientText(name, [palette.primary, palette.accent]))
|
|
347
|
+
.join(brand.dim(' '));
|
|
348
|
+
}
|
|
349
|
+
function appendInfoRows(lines, innerWidth, options, lw) {
|
|
206
350
|
if (options.project) {
|
|
207
|
-
lines.push('
|
|
208
|
-
bodyLine(brand.accent('◇ ') +
|
|
209
|
-
brand.muted(padRight(t('banner.label.mode').trim(), LABEL_WIDTH)) +
|
|
210
|
-
' ' +
|
|
211
|
-
modeChip(options.project.type), innerWidth));
|
|
351
|
+
lines.push(cardRow(infoRow(t('banner.label.mode').trim(), modeChip(options.project.type), lw), innerWidth));
|
|
212
352
|
}
|
|
213
353
|
if (options.projectRoot) {
|
|
214
|
-
|
|
215
|
-
|
|
354
|
+
const pathValue = brand.primary('▶') +
|
|
355
|
+
brand.dim(' ') +
|
|
356
|
+
brand.dim(truncateText(homify(options.projectRoot), innerWidth - lw - 8));
|
|
357
|
+
lines.push(cardRow(infoRow(t('banner.label.path').trim(), pathValue, lw), innerWidth));
|
|
216
358
|
}
|
|
217
|
-
|
|
218
|
-
if (pulseWidth > 8) {
|
|
219
|
-
const filled = Math.max(1, Math.min(pulseWidth, Math.round(pulseWidth * ready.ratio)));
|
|
220
|
-
const pulse = pulseBar(pulseWidth, filled);
|
|
221
|
-
const pulseStatus = ready.ready
|
|
222
|
-
? t('banner.pulseReady').trim()
|
|
223
|
-
: t('banner.pulsePending').trim();
|
|
224
|
-
const pulseLine = brand.accent('◇ ') +
|
|
225
|
-
brand.muted(padRight(t('banner.pulseLabel').trim(), LABEL_WIDTH)) +
|
|
226
|
-
' ' +
|
|
227
|
-
pulse +
|
|
228
|
-
' ' +
|
|
229
|
-
(ready.ready ? brand.success(pulseStatus) : brand.warn(pulseStatus));
|
|
230
|
-
lines.push(' ' + bodyLine(pulseLine, innerWidth));
|
|
231
|
-
}
|
|
232
|
-
lines.push(' ' + emptyBodyLine(innerWidth));
|
|
233
|
-
lines.push(' ' + bottomBorder(cardWidth));
|
|
234
|
-
lines.push(' ' + commandRail());
|
|
235
|
-
lines.push('');
|
|
236
|
-
for (const line of lines)
|
|
237
|
-
console.log(line);
|
|
359
|
+
appendComponentStatusRows(lines, innerWidth, options.status, lw);
|
|
238
360
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
const used = visibleLength(left) + visibleLength(right);
|
|
245
|
-
const gap = Math.max(2, innerWidth - used);
|
|
246
|
-
return ' '.repeat(gap);
|
|
247
|
-
}
|
|
248
|
-
/**
|
|
249
|
-
* Compact cyber banner for terminals that cannot host the full ASCII logo.
|
|
250
|
-
*/
|
|
251
|
-
function printCompactBanner(version, options) {
|
|
252
|
-
const cols = terminalWidth();
|
|
253
|
-
const cardWidth = Math.min(cols - 2, 76);
|
|
254
|
-
const innerWidth = cardWidth - 4;
|
|
361
|
+
function composeBannerLines(version, options, { showAscii, width, startup } = {}) {
|
|
362
|
+
const innerWidth = width - 4;
|
|
363
|
+
const systemState = startup && !startup.ready ? 'pending' : resolveSystemState(options);
|
|
364
|
+
const components = (options.status && options.status.components) || [];
|
|
365
|
+
const lw = computeLabelWidth(components);
|
|
255
366
|
const lines = [];
|
|
256
367
|
lines.push('');
|
|
257
|
-
lines.push(
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
const ready = bannerReadyState(options);
|
|
261
|
-
const wordmark = brand.primary('▌▍▎ ') +
|
|
262
|
-
gradientText('REACTPRESS', [palette.pink, palette.primary, palette.accent], {
|
|
263
|
-
bold: true,
|
|
264
|
-
}) +
|
|
265
|
-
brand.primary(' ▎▍▌');
|
|
266
|
-
const stateLabel = ready.ready
|
|
267
|
-
? brand.success(t('banner.systemOnline').trim())
|
|
268
|
-
: brand.warn(t('banner.systemPending').trim());
|
|
269
|
-
const right = statusLights(ready.ready ? 'online' : 'pending') +
|
|
270
|
-
' ' +
|
|
271
|
-
brand.dim(t('banner.systemLabel').trim() + ' ') +
|
|
272
|
-
stateLabel;
|
|
273
|
-
lines.push(' ' + bodyLine(wordmark + spacer(wordmark, right, innerWidth) + right, innerWidth));
|
|
274
|
-
const subtitle = chalk.bold(brand.accent('◆ ')) +
|
|
275
|
-
gradientText(t('banner.subtitle').trim(), [palette.accent, palette.primary, palette.pink], {
|
|
276
|
-
bold: true,
|
|
277
|
-
});
|
|
278
|
-
lines.push(' ' + bodyLine(subtitle, innerWidth));
|
|
279
|
-
lines.push(' ' + emptyBodyLine(innerWidth));
|
|
280
|
-
if (options.project) {
|
|
281
|
-
lines.push(' ' +
|
|
282
|
-
bodyLine(brand.accent('◇ ') +
|
|
283
|
-
brand.muted(padRight(t('banner.label.mode').trim(), LABEL_WIDTH)) +
|
|
284
|
-
' ' +
|
|
285
|
-
modeChip(options.project.type), innerWidth));
|
|
368
|
+
lines.push(cardTop(width));
|
|
369
|
+
for (const row of headerRows(innerWidth, version, systemState)) {
|
|
370
|
+
lines.push(cardRow(row, innerWidth));
|
|
286
371
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
372
|
+
lines.push(cardGap(innerWidth));
|
|
373
|
+
if (showAscii) {
|
|
374
|
+
const logoLines = startup
|
|
375
|
+
? renderAnimatedLogoLines(startup.frame || 0, 1)
|
|
376
|
+
: renderLogoLines();
|
|
377
|
+
for (const logoLine of logoLines) {
|
|
378
|
+
lines.push(cardRow(centerLine(logoLine, innerWidth), innerWidth));
|
|
379
|
+
}
|
|
380
|
+
lines.push(cardGap(innerWidth));
|
|
381
|
+
lines.push(cardRow(centerLine(taglineRow(), innerWidth), innerWidth));
|
|
382
|
+
if (startup && !startup.ready) {
|
|
383
|
+
lines.push(cardRow(startupPulseRow(innerWidth, {
|
|
384
|
+
completed: startup.completed,
|
|
385
|
+
total: startup.total,
|
|
386
|
+
ready: startup.ready,
|
|
387
|
+
frame: startup.frame || 0,
|
|
388
|
+
}), innerWidth));
|
|
389
|
+
}
|
|
290
390
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
(ready.ready ? brand.success(pulseStatus) : brand.warn(pulseStatus));
|
|
304
|
-
lines.push(' ' + bodyLine(pulseLine, innerWidth));
|
|
391
|
+
else {
|
|
392
|
+
const wordmark = startup ? renderAnimatedWordmark(startup.frame || 0) : wordmarkHero();
|
|
393
|
+
lines.push(cardRow(centerLine(wordmark, innerWidth), innerWidth));
|
|
394
|
+
lines.push(cardRow(centerLine(taglineRow(), innerWidth), innerWidth));
|
|
395
|
+
if (startup && !startup.ready) {
|
|
396
|
+
lines.push(cardRow(startupPulseRow(innerWidth, {
|
|
397
|
+
completed: startup.completed,
|
|
398
|
+
total: startup.total,
|
|
399
|
+
ready: startup.ready,
|
|
400
|
+
frame: startup.frame || 0,
|
|
401
|
+
}), innerWidth));
|
|
402
|
+
}
|
|
305
403
|
}
|
|
306
|
-
lines.push(
|
|
307
|
-
lines
|
|
308
|
-
lines.push(
|
|
404
|
+
lines.push(cardRow(softRule(innerWidth), innerWidth));
|
|
405
|
+
appendInfoRows(lines, innerWidth, options, lw);
|
|
406
|
+
lines.push(cardBottom(width));
|
|
407
|
+
lines.push(` ${commandRail()}`);
|
|
309
408
|
lines.push('');
|
|
409
|
+
return lines;
|
|
410
|
+
}
|
|
411
|
+
function printCardBanner(version, options, { showAscii, width, startup } = {}) {
|
|
412
|
+
const lines = composeBannerLines(version, options, { showAscii, width, startup });
|
|
310
413
|
for (const line of lines)
|
|
311
414
|
console.log(line);
|
|
312
415
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
416
|
+
function printCompactServiceStatus(status) {
|
|
417
|
+
if (!status || !Array.isArray(status.components))
|
|
418
|
+
return;
|
|
419
|
+
const lw = computeLabelWidth(status.components);
|
|
420
|
+
for (const component of status.components) {
|
|
421
|
+
console.log(` ${brand.muted(padRight(componentLabel(component), lw))}${brand.border('│ ')}${componentStatusPill(component)}`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
function printCompactBanner(version, options) {
|
|
425
|
+
const systemState = resolveSystemState(options);
|
|
321
426
|
console.log('');
|
|
322
|
-
console.log(` ${
|
|
323
|
-
console.log(` ${brand.
|
|
324
|
-
|
|
325
|
-
|
|
427
|
+
console.log(` ${macTrafficLights()} ${brand.dim(`reactpress · v${version}`)} ${systemStatusBadge(systemState)}`);
|
|
428
|
+
console.log(` ${brand.muted(icon.link)} ${hyperlink(REPO_URL, brand.dim.underline(REPO_DISPLAY))}`);
|
|
429
|
+
console.log('');
|
|
430
|
+
for (const logoLine of renderLogoLines()) {
|
|
431
|
+
console.log(` ${logoLine}`);
|
|
326
432
|
}
|
|
433
|
+
console.log(` ${centerLine(taglineRow(), bannerColumns() - 4)}`);
|
|
434
|
+
if (options.project)
|
|
435
|
+
console.log(` ${modeChip(options.project.type)}`);
|
|
327
436
|
if (options.projectRoot) {
|
|
328
|
-
console.log(` ${
|
|
437
|
+
console.log(` ${brand.primary('▶')} ${brand.dim(homify(options.projectRoot))}`);
|
|
329
438
|
}
|
|
330
|
-
|
|
439
|
+
printCompactServiceStatus(options.status);
|
|
440
|
+
console.log(` ${commandRail()}`);
|
|
331
441
|
console.log('');
|
|
332
442
|
}
|
|
333
|
-
/**
|
|
334
|
-
* Print the top-of-screen banner. Adaptive to terminal width: collapses to a
|
|
335
|
-
* single-line greeting on very narrow terminals, otherwise renders a bordered
|
|
336
|
-
* cyber-card with the full ANSI Shadow logo when there is room.
|
|
337
|
-
*
|
|
338
|
-
* @param {{
|
|
339
|
-
* projectRoot?: string,
|
|
340
|
-
* project?: { type: string, hasClient: boolean, hasServerSource: boolean }
|
|
341
|
-
* }} [options]
|
|
342
|
-
*/
|
|
343
443
|
function printBanner(options = {}) {
|
|
344
444
|
const version = getCliVersion();
|
|
345
|
-
const cols =
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
return;
|
|
349
|
-
}
|
|
350
|
-
if (cols < LOGO_WIDTH + 10) {
|
|
445
|
+
const cols = bannerColumns();
|
|
446
|
+
const { showAscii, width } = resolveLayout(cols);
|
|
447
|
+
if (cols < CARD_MIN) {
|
|
351
448
|
printCompactBanner(version, options);
|
|
352
449
|
return;
|
|
353
450
|
}
|
|
354
|
-
|
|
451
|
+
printCardBanner(version, options, { showAscii, width });
|
|
355
452
|
}
|
|
356
|
-
/**
|
|
357
|
-
* Box helper retained for backwards compatibility: a few callers still
|
|
358
|
-
* import `box` from this module to wrap arbitrary multi-line content.
|
|
359
|
-
*/
|
|
360
453
|
function box(lines, { width } = {}) {
|
|
361
454
|
const innerWidth = width
|
|
362
455
|
? width - 4
|
|
363
456
|
: lines.reduce((max, line) => Math.max(max, visibleLength(line)), 0);
|
|
364
|
-
const horizontal = '
|
|
365
|
-
const top = brand.
|
|
366
|
-
const bottom = brand.
|
|
457
|
+
const horizontal = '─'.repeat(innerWidth + 2);
|
|
458
|
+
const top = brand.border(` ╭${horizontal}╮`);
|
|
459
|
+
const bottom = brand.border(` ╰${horizontal}╯`);
|
|
367
460
|
const body = lines.map((line) => {
|
|
368
461
|
const padded = padRight(line, innerWidth);
|
|
369
|
-
return brand.
|
|
462
|
+
return brand.border(' │ ') + padded + brand.border(' │');
|
|
370
463
|
});
|
|
371
464
|
return [top, ...body, bottom];
|
|
372
465
|
}
|
|
373
|
-
module.exports = {
|
|
466
|
+
module.exports = {
|
|
467
|
+
printBanner,
|
|
468
|
+
visibleLength,
|
|
469
|
+
padRight,
|
|
470
|
+
TECH_LOGO,
|
|
471
|
+
LOGO_GRADIENTS,
|
|
472
|
+
LOGO_WIDTH,
|
|
473
|
+
box,
|
|
474
|
+
commandRail,
|
|
475
|
+
renderLogoLines,
|
|
476
|
+
renderAnimatedLogoLines,
|
|
477
|
+
renderAnimatedWordmark,
|
|
478
|
+
composeBannerLines,
|
|
479
|
+
startupPulseRow,
|
|
480
|
+
resolveLayout,
|
|
481
|
+
bannerColumns,
|
|
482
|
+
titleBarRow,
|
|
483
|
+
macTrafficLights,
|
|
484
|
+
deriveSystemState,
|
|
485
|
+
resolveSystemState,
|
|
486
|
+
CORE_SYSTEM_SERVICES,
|
|
487
|
+
};
|
|
374
488
|
//# sourceMappingURL=banner.js.map
|