@wolfstar/cli 0.2.0-next-20260905171401 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +45 -15
  2. package/dist/{build-CvSvbsYN.js → build-DbGs9-s8.js} +4 -4
  3. package/dist/{build-CvSvbsYN.js.map → build-DbGs9-s8.js.map} +1 -1
  4. package/dist/{build-B6xw00Ee.js → build-DkMulM4f.js} +2 -2
  5. package/dist/{build-B6xw00Ee.js.map → build-DkMulM4f.js.map} +1 -1
  6. package/dist/{builders-D97Uqjwg.js → builders-NkAoPRdw.js} +2 -2
  7. package/dist/{builders-D97Uqjwg.js.map → builders-NkAoPRdw.js.map} +1 -1
  8. package/dist/cli.js +6 -6
  9. package/dist/{codegen-mYkL-GBf.js → codegen-1kW_OnrL.js} +2 -2
  10. package/dist/{codegen-mYkL-GBf.js.map → codegen-1kW_OnrL.js.map} +1 -1
  11. package/dist/{codegen-DjPOn7cp.js → codegen-DHQH83M3.js} +2 -2
  12. package/dist/{codegen-DjPOn7cp.js.map → codegen-DHQH83M3.js.map} +1 -1
  13. package/dist/{commands-Dw2LFWV-.js → commands-BkZNThm8.js} +2 -2
  14. package/dist/{commands-Dw2LFWV-.js.map → commands-BkZNThm8.js.map} +1 -1
  15. package/dist/{commands-BkGKs_Bg.js → commands-DwqViJ2V.js} +4 -4
  16. package/dist/{commands-BkGKs_Bg.js.map → commands-DwqViJ2V.js.map} +1 -1
  17. package/dist/{dev-BCgaVd0m.js → dev-BazJrPZ3.js} +2 -2
  18. package/dist/{dev-BCgaVd0m.js.map → dev-BazJrPZ3.js.map} +1 -1
  19. package/dist/{dev-CWA_JVTb.js → dev-C6HogNjR.js} +80 -65
  20. package/dist/dev-C6HogNjR.js.map +1 -0
  21. package/dist/index.d.ts +4 -0
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +1 -1
  24. package/dist/{info-Czh4LUsD.js → info-BrxKCcUW.js} +2 -2
  25. package/dist/{info-Czh4LUsD.js.map → info-BrxKCcUW.js.map} +1 -1
  26. package/dist/{info-BqxpktLG.js → info-rYRLITnv.js} +2 -2
  27. package/dist/{info-BqxpktLG.js.map → info-rYRLITnv.js.map} +1 -1
  28. package/dist/log-buffer-D-nj2ZEU.js +72 -0
  29. package/dist/log-buffer-D-nj2ZEU.js.map +1 -0
  30. package/dist/{output-mode-ChrOf0LO.js → output-mode-Cxes6YUW.js} +4 -2
  31. package/dist/output-mode-Cxes6YUW.js.map +1 -0
  32. package/dist/{prepare-BXcm2Ukp.js → prepare-90XQCoak.js} +1 -1
  33. package/dist/{prepare-D5NZ0w4t.js → prepare-CHLrneYU.js} +2 -2
  34. package/dist/{prepare-D5NZ0w4t.js.map → prepare-CHLrneYU.js.map} +1 -1
  35. package/dist/{prepare-CVf88_Or.js → prepare-hOwjyelr.js} +2 -2
  36. package/dist/{prepare-CVf88_Or.js.map → prepare-hOwjyelr.js.map} +1 -1
  37. package/dist/{tsdown-BEFq2API.js → tsdown-Do8vjnIK.js} +23 -6
  38. package/dist/tsdown-Do8vjnIK.js.map +1 -0
  39. package/dist/tui-C2P91TgW.js +938 -0
  40. package/dist/tui-C2P91TgW.js.map +1 -0
  41. package/package.json +7 -6
  42. package/dist/dev-CWA_JVTb.js.map +0 -1
  43. package/dist/output-mode-ChrOf0LO.js.map +0 -1
  44. package/dist/tsdown-BEFq2API.js.map +0 -1
  45. package/dist/tui-BTRFsLQR.js +0 -770
  46. package/dist/tui-BTRFsLQR.js.map +0 -1
@@ -1,770 +0,0 @@
1
- import { Box, Static, Text, render, useApp, useInput, useWindowSize } from "ink";
2
- import { createContext, useContext, useEffect, useMemo, useState } from "react";
3
- import { jsx, jsxs } from "react/jsx-runtime";
4
-
5
- //#region src/renderers/ink/theme.tsx
6
- /**
7
- * Whether the UI may use colour. `stars dev` decides this once (`NO_COLOR`, `FORCE_COLOR`, TTY detection) and every
8
- * component reads it from here instead of re-deriving it.
9
- */
10
- const ColorContext = createContext(true);
11
- function ColorProvider({ color, children }) {
12
- return /* @__PURE__ */ jsx(ColorContext, {
13
- value: color,
14
- children
15
- });
16
- }
17
- /**
18
- * Returns a colour name only when colour is enabled, so `<Text color={useColor()('green')}>` degrades to plain text.
19
- */
20
- function useColor() {
21
- const enabled = useContext(ColorContext);
22
- return (name) => enabled ? name : void 0;
23
- }
24
-
25
- //#endregion
26
- //#region src/renderers/ink/components/HelpOverlay.tsx
27
- const KEYS = [
28
- ["r", "restart the bot now"],
29
- ["l", "open the logs (scroll, filter by source or level)"],
30
- ["q / Ctrl+C", "stop the bot and quit"],
31
- ["?", "toggle this help"]
32
- ];
33
- /** The static keys reference, opened with `?` from the pinned panel. */
34
- function HelpOverlay({ height, onClose }) {
35
- const paint = useColor();
36
- useInput(() => onClose());
37
- return /* @__PURE__ */ jsxs(Box, {
38
- flexDirection: "column",
39
- height,
40
- children: [
41
- /* @__PURE__ */ jsx(Text, {
42
- bold: true,
43
- children: "Keys"
44
- }),
45
- /* @__PURE__ */ jsx(Text, {
46
- dimColor: true,
47
- children: "─".repeat(60)
48
- }),
49
- KEYS.map(([key, description]) => /* @__PURE__ */ jsxs(Box, {
50
- gap: 1,
51
- children: [/* @__PURE__ */ jsx(Text, {
52
- bold: true,
53
- color: paint("white"),
54
- children: key.padEnd(14)
55
- }), /* @__PURE__ */ jsx(Text, {
56
- dimColor: true,
57
- children: description
58
- })]
59
- }, key)),
60
- /* @__PURE__ */ jsx(Box, { flexGrow: 1 }),
61
- /* @__PURE__ */ jsx(Text, {
62
- dimColor: true,
63
- children: "press any key to close"
64
- })
65
- ]
66
- });
67
- }
68
-
69
- //#endregion
70
- //#region src/renderers/ink/hooks/useLogVersion.ts
71
- /** Bumps whenever the buffer changes, so a consumer can re-derive a filtered view without re-filtering on every render. */
72
- function useLogVersion(logs) {
73
- const [version, setVersion] = useState(0);
74
- useEffect(() => {
75
- const bump = () => setVersion((current) => current + 1);
76
- logs.on("entry", bump);
77
- logs.on("clear", bump);
78
- return () => {
79
- logs.off("entry", bump);
80
- logs.off("clear", bump);
81
- };
82
- }, [logs]);
83
- return version;
84
- }
85
-
86
- //#endregion
87
- //#region src/renderers/ink/components/LogLine.tsx
88
- const SOURCE_COLORS = {
89
- stars: "cyan",
90
- build: "magenta",
91
- tsc: "cyan",
92
- tunnel: "yellow"
93
- };
94
- /** One committed log line, formatted the same way plain mode prints it, so scrollback reads identically either way. */
95
- function LogLine({ entry }) {
96
- const paint = useColor();
97
- const source = SOURCE_COLORS[entry.source];
98
- const level = entry.level === "error" ? "red" : entry.level === "warn" ? "yellow" : entry.level === "success" ? "green" : void 0;
99
- if (!source) return /* @__PURE__ */ jsx(Text, {
100
- color: level ? paint(level) : void 0,
101
- children: entry.text
102
- });
103
- return /* @__PURE__ */ jsxs(Text, { children: [
104
- /* @__PURE__ */ jsx(Text, {
105
- color: paint(source),
106
- children: entry.source
107
- }),
108
- " ",
109
- /* @__PURE__ */ jsx(Text, {
110
- color: level ? paint(level) : void 0,
111
- children: entry.text
112
- })
113
- ] });
114
- }
115
-
116
- //#endregion
117
- //#region src/renderers/ink/components/LogBrowser.tsx
118
- const SOURCE_FILTERS = [
119
- null,
120
- "app",
121
- "build",
122
- "tsc",
123
- "tunnel",
124
- "stars"
125
- ];
126
- const LEVEL_FILTERS = [
127
- null,
128
- "warn",
129
- "error"
130
- ];
131
- /**
132
- * The full, filterable log history, opened with `l` from the panel. The pinned panel only ever shows a live count
133
- * of warnings/errors (Nuxt CLI v4's own dev panel does the same); browsing what actually happened lives here.
134
- */
135
- function LogBrowser({ service, height, onClose }) {
136
- const paint = useColor();
137
- const [sourceIndex, setSourceIndex] = useState(0);
138
- const [levelIndex, setLevelIndex] = useState(0);
139
- const [scroll, setScroll] = useState(0);
140
- const source = SOURCE_FILTERS[sourceIndex] ?? null;
141
- const level = LEVEL_FILTERS[levelIndex] ?? null;
142
- const version = useLogVersion(service.logs);
143
- const entries = useMemo(() => service.logs.filter({
144
- source,
145
- level
146
- }), [
147
- service,
148
- source,
149
- level,
150
- version
151
- ]);
152
- const bodyHeight = Math.max(1, height - 3);
153
- const maxScroll = Math.max(0, entries.length - bodyHeight);
154
- const clamped = Math.min(scroll, maxScroll);
155
- const end = entries.length - clamped;
156
- const visible = entries.slice(Math.max(0, end - bodyHeight), end);
157
- useInput((input, key) => {
158
- if (key.escape || input === "l" || input === "q") return onClose();
159
- if (key.ctrl && input === "c") return onClose();
160
- switch (input) {
161
- case "f":
162
- setSourceIndex((current) => (current + 1) % SOURCE_FILTERS.length);
163
- return setScroll(0);
164
- case "e":
165
- setLevelIndex((current) => (current + 1) % LEVEL_FILTERS.length);
166
- return setScroll(0);
167
- case "c": return service.logs.clear();
168
- }
169
- if (key.downArrow || input === "j") return setScroll((current) => Math.max(0, current - 1));
170
- if (key.upArrow || input === "k") return setScroll((current) => Math.min(maxScroll, current + 1));
171
- if (key.pageDown) return setScroll((current) => Math.max(0, current - bodyHeight));
172
- if (key.pageUp) return setScroll((current) => Math.min(maxScroll, current + bodyHeight));
173
- if (key.end) return setScroll(0);
174
- });
175
- return /* @__PURE__ */ jsxs(Box, {
176
- flexDirection: "column",
177
- height,
178
- children: [
179
- /* @__PURE__ */ jsxs(Box, {
180
- gap: 1,
181
- children: [/* @__PURE__ */ jsx(Text, {
182
- bold: true,
183
- children: "stars dev logs"
184
- }), /* @__PURE__ */ jsx(Text, {
185
- dimColor: true,
186
- children: `filter ${source ?? "all"}${level ? `, level ≥ ${level}` : ""}`
187
- })]
188
- }),
189
- /* @__PURE__ */ jsx(Text, {
190
- dimColor: true,
191
- children: "─".repeat(60)
192
- }),
193
- /* @__PURE__ */ jsx(Box, {
194
- flexDirection: "column",
195
- height: bodyHeight,
196
- children: visible.length === 0 ? /* @__PURE__ */ jsx(Text, {
197
- dimColor: true,
198
- children: "no logs yet"
199
- }) : visible.map((entry) => /* @__PURE__ */ jsx(LogLine, { entry }, entry.id))
200
- }),
201
- /* @__PURE__ */ jsxs(Box, {
202
- gap: 2,
203
- children: [
204
- /* @__PURE__ */ jsxs(Text, { children: [
205
- /* @__PURE__ */ jsx(Text, {
206
- bold: true,
207
- color: paint("white"),
208
- children: "f"
209
- }),
210
- " ",
211
- /* @__PURE__ */ jsx(Text, {
212
- dimColor: true,
213
- children: "source"
214
- })
215
- ] }),
216
- /* @__PURE__ */ jsxs(Text, { children: [
217
- /* @__PURE__ */ jsx(Text, {
218
- bold: true,
219
- color: paint("white"),
220
- children: "e"
221
- }),
222
- " ",
223
- /* @__PURE__ */ jsx(Text, {
224
- dimColor: true,
225
- children: "errors"
226
- })
227
- ] }),
228
- /* @__PURE__ */ jsxs(Text, { children: [
229
- /* @__PURE__ */ jsx(Text, {
230
- bold: true,
231
- color: paint("white"),
232
- children: "c"
233
- }),
234
- " ",
235
- /* @__PURE__ */ jsx(Text, {
236
- dimColor: true,
237
- children: "clear"
238
- })
239
- ] }),
240
- /* @__PURE__ */ jsxs(Text, { children: [
241
- /* @__PURE__ */ jsx(Text, {
242
- bold: true,
243
- color: paint("white"),
244
- children: "↑↓"
245
- }),
246
- " ",
247
- /* @__PURE__ */ jsx(Text, {
248
- dimColor: true,
249
- children: "scroll"
250
- })
251
- ] }),
252
- /* @__PURE__ */ jsxs(Text, { children: [
253
- /* @__PURE__ */ jsx(Text, {
254
- bold: true,
255
- color: paint("white"),
256
- children: "q / esc / l"
257
- }),
258
- " ",
259
- /* @__PURE__ */ jsx(Text, {
260
- dimColor: true,
261
- children: "close"
262
- })
263
- ] })
264
- ]
265
- })
266
- ]
267
- });
268
- }
269
-
270
- //#endregion
271
- //#region src/renderers/panel-logic.ts
272
- /**
273
- * The badge and standing description for the panel's status row, mirroring the states Nuxt CLI v4's own dev panel
274
- * cycles through (`STARTING`/`BUILDING`/`RESTART`/`READY`/`ERROR`).
275
- */
276
- function describeBadge(status) {
277
- if (status.build === "building") return {
278
- badge: "building",
279
- note: "compiling changes"
280
- };
281
- if (status.process === "starting") return {
282
- badge: "starting",
283
- note: "starting the bot"
284
- };
285
- if (status.process === "stopping") return {
286
- badge: "restarting",
287
- note: "restarting the bot"
288
- };
289
- if (status.build === "failed") return {
290
- badge: "error",
291
- note: "build failed, waiting for changes"
292
- };
293
- if (status.process === "crashed") return {
294
- badge: "error",
295
- note: "the bot crashed, press r to restart"
296
- };
297
- if (status.process === "running") return {
298
- badge: "ready",
299
- note: "watching for changes"
300
- };
301
- return {
302
- badge: "starting",
303
- note: "waiting for the first build"
304
- };
305
- }
306
- /** Formats a duration the way a dev session reads it: `mm:ss` under an hour, `h:mm:ss` above. */
307
- function formatDuration(milliseconds) {
308
- const total = Math.max(0, Math.floor(milliseconds / 1e3));
309
- const seconds = total % 60;
310
- const minutes = Math.floor(total / 60) % 60;
311
- const hours = Math.floor(total / 3600);
312
- const pad = (value) => value.toString().padStart(2, "0");
313
- return hours > 0 ? `${hours}:${pad(minutes)}:${pad(seconds)}` : `${pad(minutes)}:${pad(seconds)}`;
314
- }
315
-
316
- //#endregion
317
- //#region src/renderers/ink/components/Panel.tsx
318
- const BADGE_COLOR = {
319
- starting: "yellow",
320
- building: "yellow",
321
- restarting: "yellow",
322
- ready: "green",
323
- error: "red"
324
- };
325
- const DEFAULT_HINTS = [
326
- {
327
- key: "r",
328
- label: "restart"
329
- },
330
- {
331
- key: "l",
332
- label: "logs"
333
- },
334
- {
335
- key: "?",
336
- label: "help"
337
- },
338
- {
339
- key: "q",
340
- label: "quit"
341
- }
342
- ];
343
- /**
344
- * The pinned dev panel: a handful of lines below the log stream, redrawn as state changes. Mirrors the blocks
345
- * Nuxt CLI v4's own dev panel shows — a wordmark, the URL with its live checks, a warning/error summary, a status
346
- * badge and the shortcut hints — kept to a fixed, small height instead of growing with the session.
347
- */
348
- function Panel({ name, status, config, uptimeMs, counters, spinner, narrow }) {
349
- const { badge, note } = describeBadge(status);
350
- return /* @__PURE__ */ jsxs(Box, {
351
- flexDirection: "column",
352
- children: [
353
- /* @__PURE__ */ jsx(Wordmark, {
354
- name,
355
- status,
356
- uptimeMs,
357
- narrow
358
- }),
359
- status.url && /* @__PURE__ */ jsx(UrlRow, {
360
- status,
361
- config,
362
- spinner,
363
- narrow
364
- }),
365
- /* @__PURE__ */ jsx(Summary, { counters }),
366
- /* @__PURE__ */ jsx(StatusRow, {
367
- badge,
368
- note
369
- }),
370
- /* @__PURE__ */ jsx(Hints, {
371
- errors: counters.errors,
372
- narrow
373
- })
374
- ]
375
- });
376
- }
377
- function Wordmark({ name, status, uptimeMs, narrow }) {
378
- const paint = useColor();
379
- return /* @__PURE__ */ jsxs(Box, {
380
- gap: narrow ? 1 : 2,
381
- children: [
382
- /* @__PURE__ */ jsx(Text, {
383
- bold: true,
384
- color: paint("yellow"),
385
- children: "★ stars dev"
386
- }),
387
- /* @__PURE__ */ jsx(Text, {
388
- dimColor: true,
389
- children: name
390
- }),
391
- !narrow && uptimeMs !== null && /* @__PURE__ */ jsx(Text, {
392
- dimColor: true,
393
- children: `up ${formatDuration(uptimeMs)}`
394
- }),
395
- !narrow && status.restarts > 0 && /* @__PURE__ */ jsx(Text, {
396
- dimColor: true,
397
- children: `restarts ${status.restarts}`
398
- })
399
- ]
400
- });
401
- }
402
- function UrlRow({ status, config, spinner, narrow }) {
403
- const paint = useColor();
404
- return /* @__PURE__ */ jsxs(Box, {
405
- gap: narrow ? 1 : 2,
406
- children: [
407
- /* @__PURE__ */ jsx(Text, {
408
- color: paint("cyan"),
409
- children: status.url
410
- }),
411
- !narrow && config.dev.health !== null && /* @__PURE__ */ jsx(Health, { status }),
412
- !narrow && config.dev.typecheck.enabled && /* @__PURE__ */ jsx(Typecheck, {
413
- status,
414
- spinner
415
- }),
416
- !narrow && status.tunnel !== "off" && /* @__PURE__ */ jsx(Tunnel, {
417
- status,
418
- spinner
419
- })
420
- ]
421
- });
422
- }
423
- function Health({ status }) {
424
- const paint = useColor();
425
- switch (status.health) {
426
- case "ok": return /* @__PURE__ */ jsx(Text, {
427
- color: paint("green"),
428
- children: "health ok"
429
- });
430
- case "down": return /* @__PURE__ */ jsx(Text, {
431
- color: paint("red"),
432
- children: "health down"
433
- });
434
- default: return /* @__PURE__ */ jsx(Text, {
435
- dimColor: true,
436
- children: "health unknown"
437
- });
438
- }
439
- }
440
- function Typecheck({ status, spinner }) {
441
- const paint = useColor();
442
- switch (status.typecheck) {
443
- case "checking": return /* @__PURE__ */ jsx(Text, {
444
- dimColor: true,
445
- children: `${spinner} types`
446
- });
447
- case "ok": return /* @__PURE__ */ jsx(Text, {
448
- color: paint("green"),
449
- children: "types ok"
450
- });
451
- case "failed": return /* @__PURE__ */ jsx(Text, {
452
- color: paint("red"),
453
- children: `types ${status.typeErrors}`
454
- });
455
- default: return /* @__PURE__ */ jsx(Text, {
456
- dimColor: true,
457
- children: "types idle"
458
- });
459
- }
460
- }
461
- function Tunnel({ status, spinner }) {
462
- const paint = useColor();
463
- switch (status.tunnel) {
464
- case "starting": return /* @__PURE__ */ jsx(Text, {
465
- dimColor: true,
466
- children: `${spinner} tunnel`
467
- });
468
- case "up": return /* @__PURE__ */ jsx(Text, {
469
- color: paint("green"),
470
- children: status.tunnelUrl ?? "tunnel up"
471
- });
472
- case "failed": return /* @__PURE__ */ jsx(Text, {
473
- color: paint("red"),
474
- children: "tunnel failed"
475
- });
476
- default: return null;
477
- }
478
- }
479
- function Summary({ counters }) {
480
- const paint = useColor();
481
- if (counters.warnings === 0 && counters.errors === 0) return null;
482
- return /* @__PURE__ */ jsxs(Box, {
483
- gap: 2,
484
- children: [counters.warnings > 0 && /* @__PURE__ */ jsx(Text, {
485
- color: paint("yellow"),
486
- children: `⚠ ${counters.warnings} ${plural(counters.warnings, "warning")}`
487
- }), counters.errors > 0 && /* @__PURE__ */ jsx(Text, {
488
- bold: true,
489
- color: paint("red"),
490
- children: `✖ ${counters.errors} ${plural(counters.errors, "error")}`
491
- })]
492
- });
493
- }
494
- function StatusRow({ badge, note }) {
495
- const paint = useColor();
496
- return /* @__PURE__ */ jsxs(Box, {
497
- gap: 1,
498
- children: [/* @__PURE__ */ jsx(Text, {
499
- backgroundColor: paint(BADGE_COLOR[badge]),
500
- color: paint("black"),
501
- bold: true,
502
- children: ` ${badge.toUpperCase()} `
503
- }), /* @__PURE__ */ jsx(Text, {
504
- dimColor: true,
505
- children: note
506
- })]
507
- });
508
- }
509
- function Hints({ errors, narrow }) {
510
- const paint = useColor();
511
- const hints = [...errors > 0 ? [{
512
- key: "e",
513
- label: "last error"
514
- }] : [], ...DEFAULT_HINTS];
515
- const shown = narrow ? hints.slice(0, 2) : hints;
516
- return /* @__PURE__ */ jsx(Box, {
517
- gap: narrow ? 1 : 2,
518
- children: shown.map(({ key, label }) => /* @__PURE__ */ jsxs(Text, { children: [
519
- /* @__PURE__ */ jsx(Text, {
520
- bold: true,
521
- color: paint("white"),
522
- children: key
523
- }),
524
- " ",
525
- /* @__PURE__ */ jsx(Text, {
526
- dimColor: true,
527
- children: label
528
- })
529
- ] }, key))
530
- });
531
- }
532
- function plural(count, word) {
533
- return count === 1 ? word : `${word}s`;
534
- }
535
-
536
- //#endregion
537
- //#region src/renderers/ink/hooks/useDevStatus.ts
538
- /**
539
- * Mirrors the service's status into React state. The service stays the single source of truth: the UI only
540
- * subscribes to it, exactly like the plain renderer does.
541
- */
542
- function useDevStatus(service) {
543
- const [status, setStatus] = useState(() => service.status);
544
- useEffect(() => {
545
- const onStatus = (next) => setStatus(next);
546
- service.on("status", onStatus);
547
- setStatus(service.status);
548
- return () => {
549
- service.off("status", onStatus);
550
- };
551
- }, [service]);
552
- return status;
553
- }
554
-
555
- //#endregion
556
- //#region src/renderers/ink/hooks/useLogCounters.ts
557
- /**
558
- * Warnings and errors logged since the last successful build, the way Nuxt CLI v4's own panel counts them: the
559
- * badge's job is the current state, this line is what happened getting there.
560
- */
561
- function useLogCounters(service) {
562
- const [counters, setCounters] = useState({
563
- warnings: 0,
564
- errors: 0
565
- });
566
- useEffect(() => {
567
- let previousBuild = service.status.build;
568
- const onEntry = (entry) => {
569
- if (entry.level === "warn") setCounters((current) => ({
570
- ...current,
571
- warnings: current.warnings + 1
572
- }));
573
- else if (entry.level === "error") setCounters((current) => ({
574
- ...current,
575
- errors: current.errors + 1
576
- }));
577
- };
578
- const onStatus = (status) => {
579
- if (status.build === "ok" && previousBuild !== "ok") setCounters({
580
- warnings: 0,
581
- errors: 0
582
- });
583
- previousBuild = status.build;
584
- };
585
- service.logs.on("entry", onEntry);
586
- service.on("status", onStatus);
587
- return () => {
588
- service.logs.off("entry", onEntry);
589
- service.off("status", onStatus);
590
- };
591
- }, [service]);
592
- return counters;
593
- }
594
-
595
- //#endregion
596
- //#region src/renderers/ink/hooks/useSpinner.ts
597
- const FRAMES = [
598
- "⠋",
599
- "⠙",
600
- "⠹",
601
- "⠸",
602
- "⠼",
603
- "⠴",
604
- "⠦",
605
- "⠧",
606
- "⠇",
607
- "⠏"
608
- ];
609
- const STILL = "•";
610
- /**
611
- * A spinner frame for whatever is in flight (a build, a tunnel coming up). `STARS_REDUCED_MOTION=1` and non-TTY
612
- * output freeze it on a static character instead.
613
- */
614
- function useSpinner(active, reducedMotion) {
615
- const [frame, setFrame] = useState(0);
616
- useEffect(() => {
617
- if (!active || reducedMotion) return;
618
- const timer = setInterval(() => setFrame((current) => (current + 1) % FRAMES.length), 80);
619
- return () => clearInterval(timer);
620
- }, [active, reducedMotion]);
621
- if (!active) return " ";
622
- return reducedMotion ? STILL : FRAMES[frame % FRAMES.length];
623
- }
624
-
625
- //#endregion
626
- //#region src/renderers/ink/hooks/useStaticLog.ts
627
- /**
628
- * The append-only stream `<Static>` prints once and never revisits, so a session's output becomes real terminal
629
- * scrollback exactly like plain mode's — the panel below stays a handful of lines, the way Nuxt CLI v4's own dev
630
- * server never mixes its log stream into the panel it pins.
631
- */
632
- function useStaticLog(service) {
633
- const [entries, setEntries] = useState(() => service.logs.entries().slice());
634
- useEffect(() => {
635
- const onEntry = (entry) => setEntries((current) => [...current, entry]);
636
- service.logs.on("entry", onEntry);
637
- return () => {
638
- service.logs.off("entry", onEntry);
639
- };
640
- }, [service]);
641
- return entries;
642
- }
643
-
644
- //#endregion
645
- //#region src/renderers/ink/hooks/useUptime.ts
646
- /**
647
- * Re-renders once a second while the bot is up, so the uptime in the header keeps counting.
648
- */
649
- function useUptime(startedAt) {
650
- const [now, setNow] = useState(() => Date.now());
651
- useEffect(() => {
652
- if (startedAt === null) return;
653
- setNow(Date.now());
654
- const timer = setInterval(() => setNow(Date.now()), 1e3);
655
- return () => clearInterval(timer);
656
- }, [startedAt]);
657
- return startedAt === null ? null : now - startedAt;
658
- }
659
-
660
- //#endregion
661
- //#region src/renderers/ink/DevApp.tsx
662
- /**
663
- * The interactive `stars dev` UI, in the style of Nuxt CLI v4's own dev server: log output streams into the
664
- * terminal's real scrollback through `<Static>` (never re-rendered, never reformatted), and a compact panel below
665
- * it — a handful of lines, redrawn as state changes — carries the status Nuxt's own summary/hints blocks show.
666
- * `l` opens the full, filterable log history and `?` the key reference, both replacing the panel until closed.
667
- */
668
- function DevApp({ service, color, reducedMotion, onQuit }) {
669
- const { exit } = useApp();
670
- const { columns, rows } = useWindowSize();
671
- const [view, setView] = useState("panel");
672
- const status = useDevStatus(service);
673
- const uptime = useUptime(status.startedAt);
674
- const counters = useLogCounters(service);
675
- const entries = useStaticLog(service);
676
- const spinning = status.build === "building" || status.process === "starting" || status.tunnel === "starting" || status.typecheck === "checking";
677
- const spinner = useSpinner(spinning, reducedMotion);
678
- const width = Math.max(20, columns);
679
- const height = Math.max(6, rows);
680
- const narrow = width < 60;
681
- const quit = () => {
682
- onQuit();
683
- exit();
684
- };
685
- useInput((input, key) => {
686
- if (view !== "panel") return;
687
- if (key.ctrl && input === "c") return quit();
688
- switch (input) {
689
- case "q": return quit();
690
- case "r":
691
- service.restart("manual");
692
- return;
693
- case "l": return setView("logs");
694
- case "?": return setView("help");
695
- }
696
- });
697
- return /* @__PURE__ */ jsxs(ColorProvider, {
698
- color,
699
- children: [/* @__PURE__ */ jsx(Static, {
700
- items: entries,
701
- children: (entry) => /* @__PURE__ */ jsx(LogLine, { entry }, entry.id)
702
- }), /* @__PURE__ */ jsxs(Box, {
703
- width,
704
- flexDirection: "column",
705
- children: [
706
- view === "logs" && /* @__PURE__ */ jsx(LogBrowser, {
707
- service,
708
- height,
709
- onClose: () => setView("panel")
710
- }),
711
- view === "help" && /* @__PURE__ */ jsx(HelpOverlay, {
712
- height,
713
- onClose: () => setView("panel")
714
- }),
715
- view === "panel" && /* @__PURE__ */ jsx(Panel, {
716
- name: service.config.packageJson?.name ?? "stars project",
717
- status,
718
- config: service.config,
719
- uptimeMs: uptime,
720
- counters,
721
- spinner,
722
- narrow
723
- })
724
- ]
725
- })]
726
- });
727
- }
728
-
729
- //#endregion
730
- //#region src/renderers/tui.tsx
731
- /**
732
- * The interactive renderer: an Ink (React) application whose log stream commits straight to the terminal's normal
733
- * scrollback (see `DevApp`'s use of `<Static>`), with a small pinned panel below it. Unlike a full-screen app, it
734
- * never touches the alternate screen buffer, so the session's output survives in the user's own terminal history.
735
- *
736
- * `start()` resolves when the user quits, which is what `stars dev` waits on before shutting the bot down.
737
- */
738
- function createTuiRenderer(service, options = {}) {
739
- const stdout = options.stdout ?? process.stdout;
740
- const stdin = options.stdin ?? process.stdin;
741
- let quit;
742
- const quitting = new Promise((resolve) => {
743
- quit = resolve;
744
- });
745
- const instance = render(/* @__PURE__ */ jsx(DevApp, {
746
- service,
747
- color: options.color ?? false,
748
- reducedMotion: options.reducedMotion ?? false,
749
- onQuit: quit
750
- }), {
751
- stdout,
752
- stdin,
753
- exitOnCtrlC: false,
754
- patchConsole: false,
755
- interactive: true,
756
- ...options.fps === void 0 ? {} : { maxFps: options.fps }
757
- });
758
- return {
759
- async start() {
760
- await Promise.race([quitting, instance.waitUntilExit()]);
761
- },
762
- stop() {
763
- instance.unmount();
764
- }
765
- };
766
- }
767
-
768
- //#endregion
769
- export { createTuiRenderer };
770
- //# sourceMappingURL=tui-BTRFsLQR.js.map