@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.
Files changed (98) hide show
  1. package/out/lib/cli-events.d.ts +12 -0
  2. package/out/lib/cli-events.d.ts.map +1 -0
  3. package/out/lib/cli-events.js +54 -0
  4. package/out/lib/cli-events.js.map +1 -0
  5. package/out/lib/command-palette.d.ts +6 -0
  6. package/out/lib/command-palette.d.ts.map +1 -0
  7. package/out/lib/command-palette.js +53 -0
  8. package/out/lib/command-palette.js.map +1 -0
  9. package/out/lib/context-status.d.ts +2 -0
  10. package/out/lib/context-status.d.ts.map +1 -0
  11. package/out/lib/context-status.js +148 -0
  12. package/out/lib/context-status.js.map +1 -0
  13. package/out/lib/design-tokens.d.ts +38 -0
  14. package/out/lib/design-tokens.d.ts.map +1 -0
  15. package/out/lib/design-tokens.js +55 -0
  16. package/out/lib/design-tokens.js.map +1 -0
  17. package/out/lib/dev-banner.js +10 -5
  18. package/out/lib/dev-banner.js.map +1 -1
  19. package/out/lib/dev.js +20 -1
  20. package/out/lib/dev.js.map +1 -1
  21. package/out/lib/i18n/index.js +5 -1
  22. package/out/lib/i18n/index.js.map +1 -1
  23. package/out/lib/i18n/strings.js +99 -41
  24. package/out/lib/i18n/strings.js.map +1 -1
  25. package/out/lib/menu-actions.d.ts +2 -0
  26. package/out/lib/menu-actions.d.ts.map +1 -0
  27. package/out/lib/menu-actions.js +418 -0
  28. package/out/lib/menu-actions.js.map +1 -0
  29. package/out/lib/nginx.js +5 -2
  30. package/out/lib/nginx.js.map +1 -1
  31. package/out/lib/remote-dev.js +5 -1
  32. package/out/lib/remote-dev.js.map +1 -1
  33. package/out/lib/shell-mode.d.ts +5 -0
  34. package/out/lib/shell-mode.d.ts.map +1 -0
  35. package/out/lib/shell-mode.js +23 -0
  36. package/out/lib/shell-mode.js.map +1 -0
  37. package/out/lib/theme-warmup.js +1 -1
  38. package/out/lib/theme-warmup.js.map +1 -1
  39. package/out/shell/app.d.ts +12 -0
  40. package/out/shell/app.d.ts.map +1 -0
  41. package/out/shell/app.js +384 -0
  42. package/out/shell/app.js.map +1 -0
  43. package/out/shell/components/brand-banner.d.ts +20 -0
  44. package/out/shell/components/brand-banner.d.ts.map +1 -0
  45. package/out/shell/components/brand-banner.js +93 -0
  46. package/out/shell/components/brand-banner.js.map +1 -0
  47. package/out/shell/components/command-palette.d.ts +10 -0
  48. package/out/shell/components/command-palette.d.ts.map +1 -0
  49. package/out/shell/components/command-palette.js +77 -0
  50. package/out/shell/components/command-palette.js.map +1 -0
  51. package/out/shell/components/dev-stepper.d.ts +8 -0
  52. package/out/shell/components/dev-stepper.d.ts.map +1 -0
  53. package/out/shell/components/dev-stepper.js +54 -0
  54. package/out/shell/components/dev-stepper.js.map +1 -0
  55. package/out/shell/components/ink-theme.d.ts +34 -0
  56. package/out/shell/components/ink-theme.d.ts.map +1 -0
  57. package/out/shell/components/ink-theme.js +86 -0
  58. package/out/shell/components/ink-theme.js.map +1 -0
  59. package/out/shell/components/menu-list.d.ts +7 -0
  60. package/out/shell/components/menu-list.d.ts.map +1 -0
  61. package/out/shell/components/menu-list.js +49 -0
  62. package/out/shell/components/menu-list.js.map +1 -0
  63. package/out/shell/components/pulse-bar.d.ts +6 -0
  64. package/out/shell/components/pulse-bar.d.ts.map +1 -0
  65. package/out/shell/components/pulse-bar.js +55 -0
  66. package/out/shell/components/pulse-bar.js.map +1 -0
  67. package/out/shell/components/status-rack.d.ts +16 -0
  68. package/out/shell/components/status-rack.d.ts.map +1 -0
  69. package/out/shell/components/status-rack.js +47 -0
  70. package/out/shell/components/status-rack.js.map +1 -0
  71. package/out/shell/hooks/use-terminal-size.d.ts +2 -0
  72. package/out/shell/hooks/use-terminal-size.d.ts.map +1 -0
  73. package/out/shell/hooks/use-terminal-size.js +27 -0
  74. package/out/shell/hooks/use-terminal-size.js.map +1 -0
  75. package/out/shell/ink-shell.mjs +12056 -0
  76. package/out/shell/lib/shell-layout.d.ts +27 -0
  77. package/out/shell/lib/shell-layout.d.ts.map +1 -0
  78. package/out/shell/lib/shell-layout.js +60 -0
  79. package/out/shell/lib/shell-layout.js.map +1 -0
  80. package/out/shell/runtime/render-ink.d.ts +2 -0
  81. package/out/shell/runtime/render-ink.d.ts.map +1 -0
  82. package/out/shell/runtime/render-ink.js +89 -0
  83. package/out/shell/runtime/render-ink.js.map +1 -0
  84. package/out/ui/banner-startup.d.ts +2 -0
  85. package/out/ui/banner-startup.d.ts.map +1 -0
  86. package/out/ui/banner-startup.js +125 -0
  87. package/out/ui/banner-startup.js.map +1 -0
  88. package/out/ui/banner.js +402 -288
  89. package/out/ui/banner.js.map +1 -1
  90. package/out/ui/interactive.js +149 -125
  91. package/out/ui/interactive.js.map +1 -1
  92. package/out/ui/startup-splash.d.ts +2 -0
  93. package/out/ui/startup-splash.d.ts.map +1 -0
  94. package/out/ui/startup-splash.js +185 -0
  95. package/out/ui/startup-splash.js.map +1 -0
  96. package/out/ui/theme.js +63 -10
  97. package/out/ui/theme.js.map +1 -1
  98. 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, terminalWidth, gradientText, pulseBar, statusLights, } = require('./theme');
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 = 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;
@@ -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
- .bgHex(palette.gray)
79
- .hex('#0B1220')
80
- .bold(` ${t('banner.mode.uninitialized')} `);
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('⟫ ') + gradientText(name, [palette.primary, palette.accent]))
169
- .join(brand.muted(' '));
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
- lines.push(' ' +
215
- bodyLine(infoRow(t('banner.label.path').trim(), homify(options.projectRoot)), 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));
216
358
  }
217
- const pulseWidth = Math.min(28, innerWidth - 18);
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
- * Pad between a left-aligned and a right-aligned segment so they sit on the
241
- * same line of the cyber card.
242
- */
243
- function spacer(left, right, innerWidth) {
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(' ' + brandedTopBorder(version, cardWidth));
258
- lines.push(' ' + bodyLine(repoSubline(innerWidth), innerWidth));
259
- lines.push(' ' + emptyBodyLine(innerWidth));
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
- if (options.projectRoot) {
288
- lines.push(' ' +
289
- 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
+ }
290
390
  }
291
- const pulseWidth = Math.min(20, innerWidth - 18);
292
- if (pulseWidth > 8) {
293
- const filled = Math.max(1, Math.min(pulseWidth, Math.round(pulseWidth * ready.ratio)));
294
- const pulse = pulseBar(pulseWidth, filled);
295
- const pulseStatus = ready.ready
296
- ? t('banner.pulseReady').trim()
297
- : t('banner.pulsePending').trim();
298
- const pulseLine = brand.accent('◇ ') +
299
- brand.muted(padRight(t('banner.pulseLabel').trim(), LABEL_WIDTH)) +
300
- ' ' +
301
- pulse +
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(' ' + emptyBodyLine(innerWidth));
307
- lines.push(' ' + bottomBorder(cardWidth));
308
- lines.push(' ' + commandRail());
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
- * Single-line banner for ultra-narrow terminals (CI logs, embedded shells).
315
- */
316
- function printMinimalBanner(version, options) {
317
- const ready = bannerReadyState(options);
318
- const wordmark = gradientText('REACTPRESS', [palette.pink, palette.primary, palette.accent], {
319
- bold: true,
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(` ${brand.primary('▌▍▎')} ${wordmark} ${brand.muted('·')} ${brand.accent(`v${version}`)} ${statusLights(ready.ready ? 'online' : 'pending')}`);
323
- console.log(` ${brand.dim(t('banner.subtitle').trim())}`);
324
- if (options.project) {
325
- 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}`);
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(` ${icon.bullet} ${brand.dim(homify(options.projectRoot))}`);
437
+ console.log(` ${brand.primary('▶')} ${brand.dim(homify(options.projectRoot))}`);
329
438
  }
330
- console.log(` ${brand.muted('↗')} ${hyperlink(REPO_URL, brand.accent.underline(REPO_URL))}`);
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 = terminalWidth();
346
- if (cols < 64) {
347
- printMinimalBanner(version, options);
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
- printWideBanner(version, options);
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 = '═'.repeat(innerWidth + 2);
365
- const top = brand.primary(` ╔${horizontal}╗`);
366
- const bottom = brand.primary(` ╚${horizontal}╝`);
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.primary(' ║ ') + padded + brand.primary(' ║');
462
+ return brand.border(' │ ') + padded + brand.border(' │');
370
463
  });
371
464
  return [top, ...body, bottom];
372
465
  }
373
- 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
+ };
374
488
  //# sourceMappingURL=banner.js.map