@fecommunity/reactpress 4.0.0-beta.1 → 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.
Files changed (102) hide show
  1. package/out/bin/reactpress.js +2 -2
  2. package/out/bin/reactpress.js.map +1 -1
  3. package/out/lib/cli-events.d.ts +12 -0
  4. package/out/lib/cli-events.d.ts.map +1 -0
  5. package/out/lib/cli-events.js +54 -0
  6. package/out/lib/cli-events.js.map +1 -0
  7. package/out/lib/command-palette.d.ts +6 -0
  8. package/out/lib/command-palette.d.ts.map +1 -0
  9. package/out/lib/command-palette.js +53 -0
  10. package/out/lib/command-palette.js.map +1 -0
  11. package/out/lib/context-status.d.ts +2 -0
  12. package/out/lib/context-status.d.ts.map +1 -0
  13. package/out/lib/context-status.js +148 -0
  14. package/out/lib/context-status.js.map +1 -0
  15. package/out/lib/design-tokens.d.ts +38 -0
  16. package/out/lib/design-tokens.d.ts.map +1 -0
  17. package/out/lib/design-tokens.js +55 -0
  18. package/out/lib/design-tokens.js.map +1 -0
  19. package/out/lib/dev-banner.js +10 -5
  20. package/out/lib/dev-banner.js.map +1 -1
  21. package/out/lib/dev.js +20 -1
  22. package/out/lib/dev.js.map +1 -1
  23. package/out/lib/i18n/index.js +5 -1
  24. package/out/lib/i18n/index.js.map +1 -1
  25. package/out/lib/i18n/strings.js +99 -41
  26. package/out/lib/i18n/strings.js.map +1 -1
  27. package/out/lib/menu-actions.d.ts +2 -0
  28. package/out/lib/menu-actions.d.ts.map +1 -0
  29. package/out/lib/menu-actions.js +418 -0
  30. package/out/lib/menu-actions.js.map +1 -0
  31. package/out/lib/nginx.js +5 -2
  32. package/out/lib/nginx.js.map +1 -1
  33. package/out/lib/paths.js +11 -0
  34. package/out/lib/paths.js.map +1 -1
  35. package/out/lib/remote-dev.js +5 -1
  36. package/out/lib/remote-dev.js.map +1 -1
  37. package/out/lib/shell-mode.d.ts +5 -0
  38. package/out/lib/shell-mode.d.ts.map +1 -0
  39. package/out/lib/shell-mode.js +23 -0
  40. package/out/lib/shell-mode.js.map +1 -0
  41. package/out/lib/theme-warmup.js +1 -1
  42. package/out/lib/theme-warmup.js.map +1 -1
  43. package/out/shell/app.d.ts +12 -0
  44. package/out/shell/app.d.ts.map +1 -0
  45. package/out/shell/app.js +384 -0
  46. package/out/shell/app.js.map +1 -0
  47. package/out/shell/components/brand-banner.d.ts +20 -0
  48. package/out/shell/components/brand-banner.d.ts.map +1 -0
  49. package/out/shell/components/brand-banner.js +93 -0
  50. package/out/shell/components/brand-banner.js.map +1 -0
  51. package/out/shell/components/command-palette.d.ts +10 -0
  52. package/out/shell/components/command-palette.d.ts.map +1 -0
  53. package/out/shell/components/command-palette.js +77 -0
  54. package/out/shell/components/command-palette.js.map +1 -0
  55. package/out/shell/components/dev-stepper.d.ts +8 -0
  56. package/out/shell/components/dev-stepper.d.ts.map +1 -0
  57. package/out/shell/components/dev-stepper.js +54 -0
  58. package/out/shell/components/dev-stepper.js.map +1 -0
  59. package/out/shell/components/ink-theme.d.ts +34 -0
  60. package/out/shell/components/ink-theme.d.ts.map +1 -0
  61. package/out/shell/components/ink-theme.js +86 -0
  62. package/out/shell/components/ink-theme.js.map +1 -0
  63. package/out/shell/components/menu-list.d.ts +7 -0
  64. package/out/shell/components/menu-list.d.ts.map +1 -0
  65. package/out/shell/components/menu-list.js +49 -0
  66. package/out/shell/components/menu-list.js.map +1 -0
  67. package/out/shell/components/pulse-bar.d.ts +6 -0
  68. package/out/shell/components/pulse-bar.d.ts.map +1 -0
  69. package/out/shell/components/pulse-bar.js +55 -0
  70. package/out/shell/components/pulse-bar.js.map +1 -0
  71. package/out/shell/components/status-rack.d.ts +16 -0
  72. package/out/shell/components/status-rack.d.ts.map +1 -0
  73. package/out/shell/components/status-rack.js +47 -0
  74. package/out/shell/components/status-rack.js.map +1 -0
  75. package/out/shell/hooks/use-terminal-size.d.ts +2 -0
  76. package/out/shell/hooks/use-terminal-size.d.ts.map +1 -0
  77. package/out/shell/hooks/use-terminal-size.js +27 -0
  78. package/out/shell/hooks/use-terminal-size.js.map +1 -0
  79. package/out/shell/ink-shell.mjs +12056 -0
  80. package/out/shell/lib/shell-layout.d.ts +27 -0
  81. package/out/shell/lib/shell-layout.d.ts.map +1 -0
  82. package/out/shell/lib/shell-layout.js +60 -0
  83. package/out/shell/lib/shell-layout.js.map +1 -0
  84. package/out/shell/runtime/render-ink.d.ts +2 -0
  85. package/out/shell/runtime/render-ink.d.ts.map +1 -0
  86. package/out/shell/runtime/render-ink.js +89 -0
  87. package/out/shell/runtime/render-ink.js.map +1 -0
  88. package/out/ui/banner-startup.d.ts +2 -0
  89. package/out/ui/banner-startup.d.ts.map +1 -0
  90. package/out/ui/banner-startup.js +125 -0
  91. package/out/ui/banner-startup.js.map +1 -0
  92. package/out/ui/banner.js +405 -275
  93. package/out/ui/banner.js.map +1 -1
  94. package/out/ui/interactive.js +149 -125
  95. package/out/ui/interactive.js.map +1 -1
  96. package/out/ui/startup-splash.d.ts +2 -0
  97. package/out/ui/startup-splash.d.ts.map +1 -0
  98. package/out/ui/startup-splash.js +185 -0
  99. package/out/ui/startup-splash.js.map +1 -0
  100. package/out/ui/theme.js +63 -10
  101. package/out/ui/theme.js.map +1 -1
  102. package/package.json +1 -1
package/out/ui/banner.js CHANGED
@@ -2,15 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  // @ts-nocheck
4
4
  const os = require('os');
5
- const path = require('path');
6
5
  const chalk = require('chalk');
7
- const { brand, icon, palette, visibleLength, padRight, terminalWidth, gradientText, pulseBar, statusLights, } = require('./theme');
6
+ const { brand, icon, palette, visibleLength, padRight, gradientText, macTrafficLights, systemStatusBadge, statusPill, pulseBar, } = require('./theme');
8
7
  const { t } = require('../lib/i18n');
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
- */
8
+ const { getCliVersion } = require('../lib/paths');
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 = 81;
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
- * Wrap text in an OSC-8 hyperlink escape so terminals that support it (iTerm2,
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;
@@ -49,310 +35,454 @@ function hyperlink(url, label) {
49
35
  return label;
50
36
  return `\u001B]8;;${url}\u0007${label}\u001B]8;;\u0007`;
51
37
  }
52
- function safeReadCliVersion() {
53
- try {
54
- return require(path.join(__dirname, '..', 'package.json')).version;
55
- }
56
- catch {
57
- return 'dev';
58
- }
59
- }
60
38
  function homify(p) {
61
39
  if (!p)
62
40
  return p;
63
41
  const home = os.homedir();
64
- if (home && p.startsWith(home)) {
42
+ if (home && p.startsWith(home))
65
43
  return '~' + p.slice(home.length);
66
- }
67
44
  return p;
68
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
+ }
69
153
  function renderLogoLines() {
70
154
  return TECH_LOGO.map((line, i) => gradientText(line, LOGO_GRADIENTS[i]));
71
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
+ }
72
331
  function modeChip(type) {
73
332
  if (type === 'monorepo') {
74
- return chalk
75
- .bgHex(palette.primary)
76
- .hex('#0B1220')
77
- .bold(` ${t('banner.mode.monorepo')} `);
333
+ return chalk.bgHex(palette.primary).hex('#0B1220').bold(` ${t('banner.mode.monorepo').trim()} `);
78
334
  }
79
335
  if (type === 'standalone') {
80
- return chalk
81
- .bgHex(palette.accent)
82
- .hex('#0B1220')
83
- .bold(` ${t('banner.mode.standalone')} `);
84
- }
85
- return chalk
86
- .bgHex(palette.gray)
87
- .hex('#0B1220')
88
- .bold(` ${t('banner.mode.uninitialized')} `);
89
- }
90
- /**
91
- * Decide how "ready" the welcome banner should look. When a fully
92
- * initialized project is detected we render the pulse bar at 100% and
93
- * report `ONLINE` status, instead of the static 70% placeholder that used
94
- * to make `doctor` runs look incomplete even when everything passed.
95
- */
96
- function bannerReadyState(options) {
97
- const type = options && options.project && options.project.type;
98
- if (type === 'monorepo' || type === 'standalone') {
99
- return { ratio: 1, ready: true };
336
+ return chalk.bgHex(palette.accent).hex('#0B1220').bold(` ${t('banner.mode.standalone').trim()} `);
100
337
  }
101
- return { ratio: 0.4, ready: false };
102
- }
103
- /**
104
- * Build the top edge of the cyber-card with a centered title block:
105
- * ╔══════════[ REACTPRESS · v3.0.3 ]══════════╗
106
- */
107
- function brandedTopBorder(version, width) {
108
- const titleBlock = brand.primary('[') +
109
- ' ' +
110
- gradientText('REACTPRESS', [palette.primary, palette.accent], { bold: true }) +
111
- ' ' +
112
- brand.muted('·') +
113
- ' ' +
114
- brand.accent(`v${version}`) +
115
- ' ' +
116
- brand.primary(']');
117
- const dashTotal = Math.max(0, width - 2 - visibleLength(titleBlock));
118
- const left = Math.floor(dashTotal / 2);
119
- const right = dashTotal - left;
120
- return (brand.primary('╔' + '═'.repeat(left)) +
121
- titleBlock +
122
- brand.primary('═'.repeat(right) + '╗'));
123
- }
124
- function bottomBorder(width) {
125
- return brand.primary('╚' + '═'.repeat(width - 2) + '╝');
126
- }
127
- function bodyLine(content, innerWidth) {
128
- const padded = padRight(content, innerWidth);
129
- return brand.primary('║ ') + padded + brand.primary(' ║');
130
- }
131
- function emptyBodyLine(innerWidth) {
132
- return bodyLine('', innerWidth);
133
- }
134
- /**
135
- * A subtle "CRT scan-line" rendered just under the logo.
136
- * ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
137
- */
138
- function scanline(width) {
139
- return brand.muted('▔'.repeat(width));
140
- }
141
- /**
142
- * Width of the left-side banner label column.
143
- *
144
- * Sized to fit our longest English label (`MODE` / `PATH` → 4 cells)
145
- * plus a 2-cell trailing gap, which also accommodates the Chinese
146
- * translations `模式` / `路径` (4 East-Asian cells each).
147
- */
148
- const LABEL_WIDTH = 6;
149
- /**
150
- * Centered, dim repo subtitle that sits directly under the top border.
151
- * Replaces the previous in-body `◇ REPO ↗ …` row, which competed visually
152
- * with the operational fields (MODE / PATH / pulse) further down.
153
- */
154
- function repoSubline(innerWidth) {
155
- const link = brand.muted('↗ ') + hyperlink(REPO_URL, brand.accent.underline(REPO_DISPLAY));
156
- const pad = Math.max(0, Math.floor((innerWidth - visibleLength(link)) / 2));
157
- return ' '.repeat(pad) + link;
158
- }
159
- /**
160
- * Single-cell-wide chip label, e.g. `◇ MODE ▸ monorepo`.
161
- */
162
- function infoRow(label, value) {
163
- return (brand.accent('◇ ') +
164
- brand.muted(padRight(label, LABEL_WIDTH)) +
165
- ' ' +
166
- brand.primary('▸ ') +
167
- brand.dim(value));
168
- }
169
- /**
170
- * Render the "command rail" navigation footer:
171
- * ⟫ init ⟫ dev ⟫ build ⟫ deploy ⟫ publish
172
- */
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
+ }
173
343
  function commandRail() {
174
344
  const items = ['init', 'dev', 'build', 'deploy', 'publish'];
175
345
  return items
176
- .map((name) => brand.primary('⟫ ') + gradientText(name, [palette.primary, palette.accent]))
177
- .join(brand.muted(' '));
178
- }
179
- /**
180
- * Wide, full-fat cyber banner: ASCII logo + scan-line + bordered card.
181
- */
182
- function printWideBanner(version, options) {
183
- const cols = terminalWidth();
184
- const cardWidth = Math.min(Math.max(LOGO_WIDTH + 8, 88), cols - 2);
185
- const innerWidth = cardWidth - 4;
186
- const lines = [];
187
- lines.push('');
188
- lines.push(' ' + brandedTopBorder(version, cardWidth));
189
- lines.push(' ' + bodyLine(repoSubline(innerWidth), innerWidth));
190
- lines.push(' ' + emptyBodyLine(innerWidth));
191
- const logoIndent = Math.max(0, Math.floor((innerWidth - LOGO_WIDTH) / 2));
192
- const indent = ' '.repeat(logoIndent);
193
- for (const logoLine of renderLogoLines()) {
194
- lines.push(' ' + bodyLine(indent + logoLine, innerWidth));
195
- }
196
- const scanWidth = Math.min(innerWidth - 2, LOGO_WIDTH);
197
- const scanIndent = ' '.repeat(Math.max(0, Math.floor((innerWidth - scanWidth) / 2)));
198
- lines.push(' ' + bodyLine(scanIndent + scanline(scanWidth), innerWidth));
199
- lines.push(' ' + emptyBodyLine(innerWidth));
200
- const ready = bannerReadyState(options);
201
- const subtitle = chalk.bold(brand.accent('◆ ')) +
202
- gradientText(t('banner.subtitle').trim(), [palette.accent, palette.primary, palette.pink], {
203
- bold: true,
204
- });
205
- const stateLabel = ready.ready
206
- ? brand.success(t('banner.systemOnline').trim())
207
- : brand.warn(t('banner.systemPending').trim());
208
- const right = statusLights(ready.ready ? 'online' : 'pending') +
209
- ' ' +
210
- brand.dim(t('banner.systemLabel').trim() + ' ') +
211
- stateLabel;
212
- lines.push(' ' + bodyLine(subtitle + spacer(subtitle, right, innerWidth) + right, innerWidth));
213
- 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) {
214
350
  if (options.project) {
215
- lines.push(' ' +
216
- bodyLine(brand.accent('◇ ') +
217
- brand.muted(padRight(t('banner.label.mode').trim(), LABEL_WIDTH)) +
218
- ' ' +
219
- modeChip(options.project.type), innerWidth));
351
+ lines.push(cardRow(infoRow(t('banner.label.mode').trim(), modeChip(options.project.type), lw), innerWidth));
220
352
  }
221
353
  if (options.projectRoot) {
222
- lines.push(' ' +
223
- bodyLine(infoRow(t('banner.label.path').trim(), homify(options.projectRoot)), innerWidth));
224
- }
225
- const pulseWidth = Math.min(28, innerWidth - 18);
226
- if (pulseWidth > 8) {
227
- const filled = Math.max(1, Math.min(pulseWidth, Math.round(pulseWidth * ready.ratio)));
228
- const pulse = pulseBar(pulseWidth, filled);
229
- const pulseStatus = ready.ready
230
- ? t('banner.pulseReady').trim()
231
- : t('banner.pulsePending').trim();
232
- const pulseLine = brand.accent('◇ ') +
233
- brand.muted(padRight(t('banner.pulseLabel').trim(), LABEL_WIDTH)) +
234
- ' ' +
235
- pulse +
236
- ' ' +
237
- (ready.ready ? brand.success(pulseStatus) : brand.warn(pulseStatus));
238
- lines.push(' ' + bodyLine(pulseLine, innerWidth));
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));
239
358
  }
240
- lines.push(' ' + emptyBodyLine(innerWidth));
241
- lines.push(' ' + bottomBorder(cardWidth));
242
- lines.push(' ' + commandRail());
243
- lines.push('');
244
- for (const line of lines)
245
- console.log(line);
359
+ appendComponentStatusRows(lines, innerWidth, options.status, lw);
246
360
  }
247
- /**
248
- * Pad between a left-aligned and a right-aligned segment so they sit on the
249
- * same line of the cyber card.
250
- */
251
- function spacer(left, right, innerWidth) {
252
- const used = visibleLength(left) + visibleLength(right);
253
- const gap = Math.max(2, innerWidth - used);
254
- return ' '.repeat(gap);
255
- }
256
- /**
257
- * Compact cyber banner for terminals that cannot host the full ASCII logo.
258
- */
259
- function printCompactBanner(version, options) {
260
- const cols = terminalWidth();
261
- const cardWidth = Math.min(cols - 2, 76);
262
- 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);
263
366
  const lines = [];
264
367
  lines.push('');
265
- lines.push(' ' + brandedTopBorder(version, cardWidth));
266
- lines.push(' ' + bodyLine(repoSubline(innerWidth), innerWidth));
267
- lines.push(' ' + emptyBodyLine(innerWidth));
268
- const ready = bannerReadyState(options);
269
- const wordmark = brand.primary('▌▍▎ ') +
270
- gradientText('REACTPRESS', [palette.pink, palette.primary, palette.accent], {
271
- bold: true,
272
- }) +
273
- brand.primary(' ▎▍▌');
274
- const lights = statusLights(ready.ready ? 'online' : 'pending');
275
- lines.push(' ' + bodyLine(wordmark + spacer(wordmark, lights, innerWidth) + lights, innerWidth));
276
- const subtitle = chalk.bold(brand.accent('◆ ')) + brand.dim(t('banner.subtitle').trim());
277
- lines.push(' ' + bodyLine(subtitle, innerWidth));
278
- lines.push(' ' + emptyBodyLine(innerWidth));
279
- if (options.project) {
280
- lines.push(' ' +
281
- bodyLine(brand.accent('◇ ') +
282
- brand.muted(padRight(t('banner.label.mode').trim(), LABEL_WIDTH)) +
283
- ' ' +
284
- 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));
285
371
  }
286
- if (options.projectRoot) {
287
- lines.push(' ' +
288
- bodyLine(infoRow(t('banner.label.path').trim(), homify(options.projectRoot)), innerWidth));
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
+ }
289
390
  }
290
- lines.push(' ' + emptyBodyLine(innerWidth));
291
- lines.push(' ' + bottomBorder(cardWidth));
292
- lines.push(' ' + commandRail());
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
+ }
403
+ }
404
+ lines.push(cardRow(softRule(innerWidth), innerWidth));
405
+ appendInfoRows(lines, innerWidth, options, lw);
406
+ lines.push(cardBottom(width));
407
+ lines.push(` ${commandRail()}`);
293
408
  lines.push('');
409
+ return lines;
410
+ }
411
+ function printCardBanner(version, options, { showAscii, width, startup } = {}) {
412
+ const lines = composeBannerLines(version, options, { showAscii, width, startup });
294
413
  for (const line of lines)
295
414
  console.log(line);
296
415
  }
297
- /**
298
- * Single-line banner for ultra-narrow terminals (CI logs, embedded shells).
299
- */
300
- function printMinimalBanner(version, options) {
301
- const ready = bannerReadyState(options);
302
- const wordmark = gradientText('REACTPRESS', [palette.pink, palette.primary, palette.accent], {
303
- bold: true,
304
- });
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);
305
426
  console.log('');
306
- console.log(` ${brand.primary('▌▍▎')} ${wordmark} ${brand.muted('·')} ${brand.accent(`v${version}`)} ${statusLights(ready.ready ? 'online' : 'pending')}`);
307
- console.log(` ${brand.dim(t('banner.subtitle').trim())}`);
308
- if (options.project) {
309
- console.log(` ${modeChip(options.project.type)}`);
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}`);
310
432
  }
433
+ console.log(` ${centerLine(taglineRow(), bannerColumns() - 4)}`);
434
+ if (options.project)
435
+ console.log(` ${modeChip(options.project.type)}`);
311
436
  if (options.projectRoot) {
312
- console.log(` ${icon.bullet} ${brand.dim(homify(options.projectRoot))}`);
437
+ console.log(` ${brand.primary('▶')} ${brand.dim(homify(options.projectRoot))}`);
313
438
  }
314
- console.log(` ${brand.muted('↗')} ${hyperlink(REPO_URL, brand.accent.underline(REPO_URL))}`);
439
+ printCompactServiceStatus(options.status);
440
+ console.log(` ${commandRail()}`);
315
441
  console.log('');
316
442
  }
317
- /**
318
- * Print the top-of-screen banner. Adaptive to terminal width: collapses to a
319
- * single-line greeting on very narrow terminals, otherwise renders a bordered
320
- * cyber-card with the full ANSI Shadow logo when there is room.
321
- *
322
- * @param {{
323
- * projectRoot?: string,
324
- * project?: { type: string, hasClient: boolean, hasServerSource: boolean }
325
- * }} [options]
326
- */
327
443
  function printBanner(options = {}) {
328
- const version = safeReadCliVersion();
329
- const cols = terminalWidth();
330
- if (cols < 64) {
331
- printMinimalBanner(version, options);
332
- return;
333
- }
334
- if (cols < LOGO_WIDTH + 10) {
444
+ const version = getCliVersion();
445
+ const cols = bannerColumns();
446
+ const { showAscii, width } = resolveLayout(cols);
447
+ if (cols < CARD_MIN) {
335
448
  printCompactBanner(version, options);
336
449
  return;
337
450
  }
338
- printWideBanner(version, options);
451
+ printCardBanner(version, options, { showAscii, width });
339
452
  }
340
- /**
341
- * Box helper retained for backwards compatibility: a few callers still
342
- * import `box` from this module to wrap arbitrary multi-line content.
343
- */
344
453
  function box(lines, { width } = {}) {
345
454
  const innerWidth = width
346
455
  ? width - 4
347
456
  : lines.reduce((max, line) => Math.max(max, visibleLength(line)), 0);
348
- const horizontal = '═'.repeat(innerWidth + 2);
349
- const top = brand.primary(` ╔${horizontal}╗`);
350
- const bottom = brand.primary(` ╚${horizontal}╝`);
457
+ const horizontal = '─'.repeat(innerWidth + 2);
458
+ const top = brand.border(` ╭${horizontal}╮`);
459
+ const bottom = brand.border(` ╰${horizontal}╯`);
351
460
  const body = lines.map((line) => {
352
461
  const padded = padRight(line, innerWidth);
353
- return brand.primary(' ║ ') + padded + brand.primary(' ║');
462
+ return brand.border(' │ ') + padded + brand.border(' │');
354
463
  });
355
464
  return [top, ...body, bottom];
356
465
  }
357
- module.exports = { printBanner, visibleLength, padRight, box };
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
+ };
358
488
  //# sourceMappingURL=banner.js.map