spawn-term 2.0.0 → 2.1.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 (49) hide show
  1. package/dist/cjs/components/App.js +79 -16
  2. package/dist/cjs/components/App.js.map +1 -1
  3. package/dist/cjs/components/CompactProcessLine.js +12 -3
  4. package/dist/cjs/components/CompactProcessLine.js.map +1 -1
  5. package/dist/cjs/components/ExpandedOutput.js +55 -0
  6. package/dist/cjs/components/ExpandedOutput.js.map +1 -0
  7. package/dist/cjs/constants.js +42 -0
  8. package/dist/cjs/constants.js.map +1 -0
  9. package/dist/cjs/createApp.js +11 -11
  10. package/dist/cjs/createApp.js.map +1 -1
  11. package/dist/cjs/lib/addLines.js +58 -4
  12. package/dist/cjs/lib/addLines.js.map +1 -1
  13. package/dist/cjs/lib/format.js +21 -0
  14. package/dist/cjs/lib/format.js.map +1 -0
  15. package/dist/cjs/src/components/CompactProcessLine.d.ts +1 -0
  16. package/dist/cjs/src/components/ExpandedOutput.d.ts +8 -0
  17. package/dist/cjs/src/constants.d.ts +7 -0
  18. package/dist/cjs/src/lib/addLines.d.ts +6 -1
  19. package/dist/cjs/src/lib/format.d.ts +2 -0
  20. package/dist/cjs/src/state/processStore.d.ts +20 -1
  21. package/dist/cjs/src/types.d.ts +6 -0
  22. package/dist/cjs/state/processStore.js +105 -1
  23. package/dist/cjs/state/processStore.js.map +1 -1
  24. package/dist/cjs/types.js.map +1 -1
  25. package/dist/esm/components/App.js +79 -14
  26. package/dist/esm/components/App.js.map +1 -1
  27. package/dist/esm/components/CompactProcessLine.js +12 -3
  28. package/dist/esm/components/CompactProcessLine.js.map +1 -1
  29. package/dist/esm/components/ExpandedOutput.js +41 -0
  30. package/dist/esm/components/ExpandedOutput.js.map +1 -0
  31. package/dist/esm/constants.js +11 -0
  32. package/dist/esm/constants.js.map +1 -0
  33. package/dist/esm/createApp.js +11 -11
  34. package/dist/esm/createApp.js.map +1 -1
  35. package/dist/esm/lib/addLines.js +32 -5
  36. package/dist/esm/lib/addLines.js.map +1 -1
  37. package/dist/esm/lib/format.js +10 -0
  38. package/dist/esm/lib/format.js.map +1 -0
  39. package/dist/esm/src/components/CompactProcessLine.d.ts +1 -0
  40. package/dist/esm/src/components/ExpandedOutput.d.ts +8 -0
  41. package/dist/esm/src/constants.d.ts +7 -0
  42. package/dist/esm/src/lib/addLines.d.ts +6 -1
  43. package/dist/esm/src/lib/format.d.ts +2 -0
  44. package/dist/esm/src/state/processStore.d.ts +20 -1
  45. package/dist/esm/src/types.d.ts +6 -0
  46. package/dist/esm/state/processStore.js +82 -1
  47. package/dist/esm/state/processStore.js.map +1 -1
  48. package/dist/esm/types.js.map +1 -1
  49. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  import type { ChildProcess, Line } from '../types.js';
2
2
  type Listener = () => void;
3
- type Mode = 'normal' | 'errorList' | 'errorDetail';
3
+ type Mode = 'normal' | 'interactive' | 'errorList' | 'errorDetail';
4
4
  declare class ProcessStore {
5
5
  private processes;
6
6
  private completedIds;
@@ -8,26 +8,45 @@ declare class ProcessStore {
8
8
  private shouldExit;
9
9
  private exitCallback;
10
10
  private mode;
11
+ private selectedIndex;
11
12
  private selectedErrorIndex;
13
+ private expandedId;
14
+ private scrollOffset;
15
+ private header;
12
16
  subscribe: (listener: Listener) => (() => void);
13
17
  getSnapshot: () => ChildProcess[];
14
18
  getRunningProcesses: () => ChildProcess[];
15
19
  getCompletedProcesses: () => ChildProcess[];
16
20
  getFailedProcesses: () => ChildProcess[];
17
21
  getRunningCount: () => number;
22
+ getMaxGroupLength: () => number;
18
23
  getDoneCount: () => number;
19
24
  getErrorCount: () => number;
20
25
  getErrorLineCount: () => number;
21
26
  getMode: () => Mode;
27
+ getSelectedIndex: () => number;
22
28
  getSelectedErrorIndex: () => number;
29
+ getExpandedId: () => string | null;
30
+ getScrollOffset: () => number;
31
+ getHeader: () => string | undefined;
32
+ getShowStatusBar: () => boolean;
33
+ getIsInteractive: () => boolean;
34
+ isAllComplete: () => boolean;
23
35
  addProcess(process: ChildProcess): void;
24
36
  updateProcess(id: string, update: Partial<ChildProcess>): void;
25
37
  appendLines(id: string, newLines: Line[]): void;
26
38
  getProcess(id: string): ChildProcess | undefined;
27
39
  setMode(mode: Mode): void;
40
+ selectNext(): void;
41
+ selectPrev(): void;
42
+ getSelectedProcess(): ChildProcess | undefined;
28
43
  selectNextError(): void;
29
44
  selectPrevError(): void;
30
45
  getSelectedError(): ChildProcess | undefined;
46
+ toggleExpand(): void;
47
+ collapse(): void;
48
+ scrollDown(maxVisible: number): void;
49
+ scrollUp(): void;
31
50
  signalExit(callback: () => void): void;
32
51
  getShouldExit: () => boolean;
33
52
  getExitCallback: () => (() => void) | null;
@@ -3,6 +3,9 @@ import type { SpawnError, SpawnResult } from 'cross-spawn-cb';
3
3
  export type TerminalOptions = {
4
4
  group?: string;
5
5
  expanded?: boolean;
6
+ header?: string;
7
+ showStatusBar?: boolean;
8
+ interactive?: boolean;
6
9
  };
7
10
  export type TerminalCallback = (error?: SpawnError, result?: SpawnResult) => undefined;
8
11
  export declare const LineType: {
@@ -21,4 +24,7 @@ export type ChildProcess = {
21
24
  state: State;
22
25
  lines: Line[];
23
26
  expanded?: boolean;
27
+ header?: string;
28
+ showStatusBar?: boolean;
29
+ interactive?: boolean;
24
30
  };
@@ -8,6 +8,7 @@ Object.defineProperty(exports, "processStore", {
8
8
  return processStore;
9
9
  }
10
10
  });
11
+ var _constantsts = require("../constants.js");
11
12
  var _typests = require("../types.js");
12
13
  function _array_like_to_array(arr, len) {
13
14
  if (len == null || len > arr.length) len = arr.length;
@@ -79,7 +80,10 @@ var ProcessStore = /*#__PURE__*/ function() {
79
80
  this.exitCallback = null;
80
81
  // UI state
81
82
  this.mode = 'normal';
83
+ this.selectedIndex = 0;
82
84
  this.selectedErrorIndex = 0;
85
+ this.expandedId = null;
86
+ this.scrollOffset = 0;
83
87
  // useSyncExternalStore API
84
88
  this.subscribe = function(listener) {
85
89
  _this.listeners.add(listener);
@@ -117,6 +121,13 @@ var ProcessStore = /*#__PURE__*/ function() {
117
121
  return p.state === 'running';
118
122
  }).length;
119
123
  };
124
+ this.getMaxGroupLength = function() {
125
+ var _Math;
126
+ if (_this.processes.length === 0) return _constantsts.DEFAULT_COLUMN_WIDTH;
127
+ return (_Math = Math).max.apply(_Math, _to_consumable_array(_this.processes.map(function(p) {
128
+ return (p.group || p.title).length;
129
+ })));
130
+ };
120
131
  this.getDoneCount = function() {
121
132
  return _this.processes.filter(function(p) {
122
133
  return p.state !== 'running';
@@ -140,9 +151,39 @@ var ProcessStore = /*#__PURE__*/ function() {
140
151
  this.getMode = function() {
141
152
  return _this.mode;
142
153
  };
154
+ this.getSelectedIndex = function() {
155
+ return _this.selectedIndex;
156
+ };
143
157
  this.getSelectedErrorIndex = function() {
144
158
  return _this.selectedErrorIndex;
145
159
  };
160
+ this.getExpandedId = function() {
161
+ return _this.expandedId;
162
+ };
163
+ this.getScrollOffset = function() {
164
+ return _this.scrollOffset;
165
+ };
166
+ // Get header
167
+ this.getHeader = function() {
168
+ return _this.header;
169
+ };
170
+ // Show status bar only if any process sets showStatusBar: true (default: false)
171
+ this.getShowStatusBar = function() {
172
+ return _this.processes.some(function(p) {
173
+ return p.showStatusBar === true;
174
+ });
175
+ };
176
+ // Interactive mode if any process has interactive: true
177
+ this.getIsInteractive = function() {
178
+ return _this.processes.some(function(p) {
179
+ return p.interactive === true;
180
+ });
181
+ };
182
+ this.isAllComplete = function() {
183
+ return _this.processes.length > 0 && _this.processes.every(function(p) {
184
+ return p.state !== 'running';
185
+ });
186
+ };
146
187
  this.getShouldExit = function() {
147
188
  return _this.shouldExit;
148
189
  };
@@ -153,6 +194,10 @@ var ProcessStore = /*#__PURE__*/ function() {
153
194
  var _proto = ProcessStore.prototype;
154
195
  // Mutations - Ink handles render throttling at 30 FPS
155
196
  _proto.addProcess = function addProcess(process) {
197
+ // Set header on first process that provides one
198
+ if (this.header === undefined && process.header !== undefined) {
199
+ this.header = process.header;
200
+ }
156
201
  this.processes = _to_consumable_array(this.processes).concat([
157
202
  process
158
203
  ]);
@@ -193,11 +238,29 @@ var ProcessStore = /*#__PURE__*/ function() {
193
238
  // UI state mutations
194
239
  _proto.setMode = function setMode(mode) {
195
240
  this.mode = mode;
196
- if (mode === 'errorList') {
241
+ if (mode === 'interactive') {
242
+ this.selectedIndex = 0;
243
+ } else if (mode === 'errorList') {
197
244
  this.selectedErrorIndex = 0;
198
245
  }
199
246
  this.notify();
200
247
  };
248
+ // Interactive mode navigation
249
+ _proto.selectNext = function selectNext() {
250
+ if (this.processes.length > 0) {
251
+ this.selectedIndex = (this.selectedIndex + 1) % this.processes.length;
252
+ this.notify();
253
+ }
254
+ };
255
+ _proto.selectPrev = function selectPrev() {
256
+ if (this.processes.length > 0) {
257
+ this.selectedIndex = (this.selectedIndex - 1 + this.processes.length) % this.processes.length;
258
+ this.notify();
259
+ }
260
+ };
261
+ _proto.getSelectedProcess = function getSelectedProcess() {
262
+ return this.processes[this.selectedIndex];
263
+ };
201
264
  _proto.selectNextError = function selectNextError() {
202
265
  var failed = this.getFailedProcesses();
203
266
  if (failed.length > 0) {
@@ -216,6 +279,43 @@ var ProcessStore = /*#__PURE__*/ function() {
216
279
  var failed = this.getFailedProcesses();
217
280
  return failed[this.selectedErrorIndex];
218
281
  };
282
+ // Expansion methods
283
+ _proto.toggleExpand = function toggleExpand() {
284
+ var selected = this.getSelectedProcess();
285
+ if (!selected) return;
286
+ if (this.expandedId === selected.id) {
287
+ // Collapse
288
+ this.expandedId = null;
289
+ this.scrollOffset = 0;
290
+ } else {
291
+ // Expand
292
+ this.expandedId = selected.id;
293
+ this.scrollOffset = 0;
294
+ }
295
+ this.notify();
296
+ };
297
+ _proto.collapse = function collapse() {
298
+ this.expandedId = null;
299
+ this.scrollOffset = 0;
300
+ this.notify();
301
+ };
302
+ _proto.scrollDown = function scrollDown(maxVisible) {
303
+ if (!this.expandedId) return;
304
+ var process = this.getProcess(this.expandedId);
305
+ if (!process) return;
306
+ var maxOffset = Math.max(0, process.lines.length - maxVisible);
307
+ if (this.scrollOffset < maxOffset) {
308
+ this.scrollOffset++;
309
+ this.notify();
310
+ }
311
+ };
312
+ _proto.scrollUp = function scrollUp() {
313
+ if (!this.expandedId) return;
314
+ if (this.scrollOffset > 0) {
315
+ this.scrollOffset--;
316
+ this.notify();
317
+ }
318
+ };
219
319
  // Exit signaling
220
320
  _proto.signalExit = function signalExit(callback) {
221
321
  this.shouldExit = true;
@@ -228,7 +328,11 @@ var ProcessStore = /*#__PURE__*/ function() {
228
328
  this.shouldExit = false;
229
329
  this.exitCallback = null;
230
330
  this.mode = 'normal';
331
+ this.selectedIndex = 0;
231
332
  this.selectedErrorIndex = 0;
333
+ this.expandedId = null;
334
+ this.scrollOffset = 0;
335
+ this.header = undefined;
232
336
  };
233
337
  _proto.notify = function notify() {
234
338
  this.listeners.forEach(function(l) {
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/state/processStore.ts"],"sourcesContent":["import type { ChildProcess, Line } from '../types.ts';\nimport { LineType } from '../types.ts';\n\ntype Listener = () => void;\ntype Mode = 'normal' | 'errorList' | 'errorDetail';\n\nclass ProcessStore {\n private processes: ChildProcess[] = [];\n private completedIds: string[] = []; // Track completion order\n private listeners = new Set<Listener>();\n private shouldExit = false;\n private exitCallback: (() => void) | null = null;\n\n // UI state\n private mode: Mode = 'normal';\n private selectedErrorIndex = 0;\n\n // useSyncExternalStore API\n subscribe = (listener: Listener): (() => void) => {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n };\n\n getSnapshot = (): ChildProcess[] => this.processes;\n\n // Filtered getters\n getRunningProcesses = (): ChildProcess[] => {\n return this.processes.filter((p) => p.state === 'running');\n };\n\n getCompletedProcesses = (): ChildProcess[] => {\n // Return in completion order\n return this.completedIds.map((id) => this.processes.find((p) => p.id === id)).filter((p): p is ChildProcess => p !== undefined);\n };\n\n getFailedProcesses = (): ChildProcess[] => {\n return this.processes.filter((p) => p.state === 'error');\n };\n\n // Counts\n getRunningCount = (): number => this.processes.filter((p) => p.state === 'running').length;\n getDoneCount = (): number => this.processes.filter((p) => p.state !== 'running').length;\n getErrorCount = (): number => this.processes.filter((p) => p.state === 'error').length;\n getErrorLineCount = (): number => {\n return this.processes.filter((p) => p.state === 'error').reduce((total, p) => total + p.lines.filter((l) => l.type === LineType.stderr).length, 0);\n };\n\n // UI state getters\n getMode = (): Mode => this.mode;\n getSelectedErrorIndex = (): number => this.selectedErrorIndex;\n\n // Mutations - Ink handles render throttling at 30 FPS\n addProcess(process: ChildProcess): void {\n this.processes = [...this.processes, process];\n this.notify();\n }\n\n updateProcess(id: string, update: Partial<ChildProcess>): void {\n const oldProcess = this.processes.find((p) => p.id === id);\n const wasRunning = oldProcess?.state === 'running';\n const isNowComplete = update.state && update.state !== 'running';\n\n this.processes = this.processes.map((p) => (p.id === id ? { ...p, ...update } : p));\n\n // Track completion order\n if (wasRunning && isNowComplete && !this.completedIds.includes(id)) {\n this.completedIds = [...this.completedIds, id];\n }\n\n this.notify();\n }\n\n appendLines(id: string, newLines: Line[]): void {\n const process = this.processes.find((p) => p.id === id);\n if (process) {\n this.updateProcess(id, { lines: process.lines.concat(newLines) });\n }\n }\n\n getProcess(id: string): ChildProcess | undefined {\n return this.processes.find((p) => p.id === id);\n }\n\n // UI state mutations\n setMode(mode: Mode): void {\n this.mode = mode;\n if (mode === 'errorList') {\n this.selectedErrorIndex = 0;\n }\n this.notify();\n }\n\n selectNextError(): void {\n const failed = this.getFailedProcesses();\n if (failed.length > 0) {\n this.selectedErrorIndex = (this.selectedErrorIndex + 1) % failed.length;\n this.notify();\n }\n }\n\n selectPrevError(): void {\n const failed = this.getFailedProcesses();\n if (failed.length > 0) {\n this.selectedErrorIndex = (this.selectedErrorIndex - 1 + failed.length) % failed.length;\n this.notify();\n }\n }\n\n getSelectedError(): ChildProcess | undefined {\n const failed = this.getFailedProcesses();\n return failed[this.selectedErrorIndex];\n }\n\n // Exit signaling\n signalExit(callback: () => void): void {\n this.shouldExit = true;\n this.exitCallback = callback;\n this.notify();\n }\n\n getShouldExit = (): boolean => this.shouldExit;\n getExitCallback = (): (() => void) | null => this.exitCallback;\n\n reset(): void {\n this.processes = [];\n this.completedIds = [];\n this.shouldExit = false;\n this.exitCallback = null;\n this.mode = 'normal';\n this.selectedErrorIndex = 0;\n }\n\n private notify(): void {\n this.listeners.forEach((l) => {\n l();\n });\n }\n}\n\nexport const processStore = new ProcessStore();\nexport type { ProcessStore };\n"],"names":["processStore","ProcessStore","processes","completedIds","listeners","Set","shouldExit","exitCallback","mode","selectedErrorIndex","subscribe","listener","add","delete","getSnapshot","getRunningProcesses","filter","p","state","getCompletedProcesses","map","id","find","undefined","getFailedProcesses","getRunningCount","length","getDoneCount","getErrorCount","getErrorLineCount","reduce","total","lines","l","type","LineType","stderr","getMode","getSelectedErrorIndex","getShouldExit","getExitCallback","addProcess","process","notify","updateProcess","update","oldProcess","wasRunning","isNowComplete","includes","appendLines","newLines","concat","getProcess","setMode","selectNextError","failed","selectPrevError","getSelectedError","signalExit","callback","reset","forEach"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/state/processStore.ts"],"sourcesContent":["import { DEFAULT_COLUMN_WIDTH } from '../constants.ts';\nimport type { ChildProcess, Line } from '../types.ts';\nimport { LineType } from '../types.ts';\n\ntype Listener = () => void;\ntype Mode = 'normal' | 'interactive' | 'errorList' | 'errorDetail';\n\nclass ProcessStore {\n private processes: ChildProcess[] = [];\n private completedIds: string[] = []; // Track completion order\n private listeners = new Set<Listener>();\n private shouldExit = false;\n private exitCallback: (() => void) | null = null;\n\n // UI state\n private mode: Mode = 'normal';\n private selectedIndex = 0;\n private selectedErrorIndex = 0;\n private expandedId: string | null = null;\n private scrollOffset = 0;\n\n // App-level display settings\n private header: string | undefined;\n\n // useSyncExternalStore API\n subscribe = (listener: Listener): (() => void) => {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n };\n\n getSnapshot = (): ChildProcess[] => this.processes;\n\n // Filtered getters\n getRunningProcesses = (): ChildProcess[] => {\n return this.processes.filter((p) => p.state === 'running');\n };\n\n getCompletedProcesses = (): ChildProcess[] => {\n // Return in completion order\n return this.completedIds.map((id) => this.processes.find((p) => p.id === id)).filter((p): p is ChildProcess => p !== undefined);\n };\n\n getFailedProcesses = (): ChildProcess[] => {\n return this.processes.filter((p) => p.state === 'error');\n };\n\n // Counts\n getRunningCount = (): number => this.processes.filter((p) => p.state === 'running').length;\n getMaxGroupLength = (): number => {\n if (this.processes.length === 0) return DEFAULT_COLUMN_WIDTH;\n return Math.max(...this.processes.map((p) => (p.group || p.title).length));\n };\n getDoneCount = (): number => this.processes.filter((p) => p.state !== 'running').length;\n getErrorCount = (): number => this.processes.filter((p) => p.state === 'error').length;\n getErrorLineCount = (): number => {\n return this.processes.filter((p) => p.state === 'error').reduce((total, p) => total + p.lines.filter((l) => l.type === LineType.stderr).length, 0);\n };\n\n // UI state getters\n getMode = (): Mode => this.mode;\n getSelectedIndex = (): number => this.selectedIndex;\n getSelectedErrorIndex = (): number => this.selectedErrorIndex;\n getExpandedId = (): string | null => this.expandedId;\n getScrollOffset = (): number => this.scrollOffset;\n // Get header\n getHeader = (): string | undefined => this.header;\n // Show status bar only if any process sets showStatusBar: true (default: false)\n getShowStatusBar = (): boolean => this.processes.some((p) => p.showStatusBar === true);\n // Interactive mode if any process has interactive: true\n getIsInteractive = (): boolean => this.processes.some((p) => p.interactive === true);\n isAllComplete = (): boolean => this.processes.length > 0 && this.processes.every((p) => p.state !== 'running');\n\n // Mutations - Ink handles render throttling at 30 FPS\n addProcess(process: ChildProcess): void {\n // Set header on first process that provides one\n if (this.header === undefined && process.header !== undefined) {\n this.header = process.header;\n }\n this.processes = [...this.processes, process];\n this.notify();\n }\n\n updateProcess(id: string, update: Partial<ChildProcess>): void {\n const oldProcess = this.processes.find((p) => p.id === id);\n const wasRunning = oldProcess?.state === 'running';\n const isNowComplete = update.state && update.state !== 'running';\n\n this.processes = this.processes.map((p) => (p.id === id ? { ...p, ...update } : p));\n\n // Track completion order\n if (wasRunning && isNowComplete && !this.completedIds.includes(id)) {\n this.completedIds = [...this.completedIds, id];\n }\n\n this.notify();\n }\n\n appendLines(id: string, newLines: Line[]): void {\n const process = this.processes.find((p) => p.id === id);\n if (process) {\n this.updateProcess(id, { lines: process.lines.concat(newLines) });\n }\n }\n\n getProcess(id: string): ChildProcess | undefined {\n return this.processes.find((p) => p.id === id);\n }\n\n // UI state mutations\n setMode(mode: Mode): void {\n this.mode = mode;\n if (mode === 'interactive') {\n this.selectedIndex = 0;\n } else if (mode === 'errorList') {\n this.selectedErrorIndex = 0;\n }\n this.notify();\n }\n\n // Interactive mode navigation\n selectNext(): void {\n if (this.processes.length > 0) {\n this.selectedIndex = (this.selectedIndex + 1) % this.processes.length;\n this.notify();\n }\n }\n\n selectPrev(): void {\n if (this.processes.length > 0) {\n this.selectedIndex = (this.selectedIndex - 1 + this.processes.length) % this.processes.length;\n this.notify();\n }\n }\n\n getSelectedProcess(): ChildProcess | undefined {\n return this.processes[this.selectedIndex];\n }\n\n selectNextError(): void {\n const failed = this.getFailedProcesses();\n if (failed.length > 0) {\n this.selectedErrorIndex = (this.selectedErrorIndex + 1) % failed.length;\n this.notify();\n }\n }\n\n selectPrevError(): void {\n const failed = this.getFailedProcesses();\n if (failed.length > 0) {\n this.selectedErrorIndex = (this.selectedErrorIndex - 1 + failed.length) % failed.length;\n this.notify();\n }\n }\n\n getSelectedError(): ChildProcess | undefined {\n const failed = this.getFailedProcesses();\n return failed[this.selectedErrorIndex];\n }\n\n // Expansion methods\n toggleExpand(): void {\n const selected = this.getSelectedProcess();\n if (!selected) return;\n\n if (this.expandedId === selected.id) {\n // Collapse\n this.expandedId = null;\n this.scrollOffset = 0;\n } else {\n // Expand\n this.expandedId = selected.id;\n this.scrollOffset = 0;\n }\n this.notify();\n }\n\n collapse(): void {\n this.expandedId = null;\n this.scrollOffset = 0;\n this.notify();\n }\n\n scrollDown(maxVisible: number): void {\n if (!this.expandedId) return;\n const process = this.getProcess(this.expandedId);\n if (!process) return;\n\n const maxOffset = Math.max(0, process.lines.length - maxVisible);\n if (this.scrollOffset < maxOffset) {\n this.scrollOffset++;\n this.notify();\n }\n }\n\n scrollUp(): void {\n if (!this.expandedId) return;\n if (this.scrollOffset > 0) {\n this.scrollOffset--;\n this.notify();\n }\n }\n\n // Exit signaling\n signalExit(callback: () => void): void {\n this.shouldExit = true;\n this.exitCallback = callback;\n this.notify();\n }\n\n getShouldExit = (): boolean => this.shouldExit;\n getExitCallback = (): (() => void) | null => this.exitCallback;\n\n reset(): void {\n this.processes = [];\n this.completedIds = [];\n this.shouldExit = false;\n this.exitCallback = null;\n this.mode = 'normal';\n this.selectedIndex = 0;\n this.selectedErrorIndex = 0;\n this.expandedId = null;\n this.scrollOffset = 0;\n this.header = undefined;\n }\n\n private notify(): void {\n this.listeners.forEach((l) => {\n l();\n });\n }\n}\n\nexport const processStore = new ProcessStore();\nexport type { ProcessStore };\n"],"names":["processStore","ProcessStore","processes","completedIds","listeners","Set","shouldExit","exitCallback","mode","selectedIndex","selectedErrorIndex","expandedId","scrollOffset","subscribe","listener","add","delete","getSnapshot","getRunningProcesses","filter","p","state","getCompletedProcesses","map","id","find","undefined","getFailedProcesses","getRunningCount","length","getMaxGroupLength","Math","DEFAULT_COLUMN_WIDTH","max","group","title","getDoneCount","getErrorCount","getErrorLineCount","reduce","total","lines","l","type","LineType","stderr","getMode","getSelectedIndex","getSelectedErrorIndex","getExpandedId","getScrollOffset","getHeader","header","getShowStatusBar","some","showStatusBar","getIsInteractive","interactive","isAllComplete","every","getShouldExit","getExitCallback","addProcess","process","notify","updateProcess","update","oldProcess","wasRunning","isNowComplete","includes","appendLines","newLines","concat","getProcess","setMode","selectNext","selectPrev","getSelectedProcess","selectNextError","failed","selectPrevError","getSelectedError","toggleExpand","selected","collapse","scrollDown","maxVisible","maxOffset","scrollUp","signalExit","callback","reset","forEach"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/types.ts"],"sourcesContent":["export type { SpawnCallback, SpawnError, SpawnOptions, SpawnResult } from 'cross-spawn-cb';\n\nimport type { SpawnError, SpawnResult } from 'cross-spawn-cb';\n\nexport type TerminalOptions = {\n group?: string;\n expanded?: boolean;\n};\n\nexport type TerminalCallback = (error?: SpawnError, result?: SpawnResult) => undefined;\n\nexport const LineType = {\n stdout: 1,\n stderr: 2,\n} as const;\n\nexport type Line = {\n type: (typeof LineType)[keyof typeof LineType];\n text: string;\n};\n\nexport type State = 'running' | 'error' | 'success';\nexport type ChildProcess = {\n id: string;\n group?: string;\n title: string;\n state: State;\n lines: Line[];\n expanded?: boolean;\n};\n"],"names":["LineType","stdout","stderr"],"mappings":";;;;+BAWaA;;;eAAAA;;;AAAN,IAAMA,WAAW;IACtBC,QAAQ;IACRC,QAAQ;AACV"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/types.ts"],"sourcesContent":["export type { SpawnCallback, SpawnError, SpawnOptions, SpawnResult } from 'cross-spawn-cb';\n\nimport type { SpawnError, SpawnResult } from 'cross-spawn-cb';\n\nexport type TerminalOptions = {\n group?: string;\n expanded?: boolean;\n header?: string;\n showStatusBar?: boolean;\n interactive?: boolean;\n};\n\nexport type TerminalCallback = (error?: SpawnError, result?: SpawnResult) => undefined;\n\nexport const LineType = {\n stdout: 1,\n stderr: 2,\n} as const;\n\nexport type Line = {\n type: (typeof LineType)[keyof typeof LineType];\n text: string;\n};\n\nexport type State = 'running' | 'error' | 'success';\nexport type ChildProcess = {\n id: string;\n group?: string;\n title: string;\n state: State;\n lines: Line[];\n expanded?: boolean;\n header?: string;\n showStatusBar?: boolean;\n interactive?: boolean;\n};\n"],"names":["LineType","stdout","stderr"],"mappings":";;;;+BAcaA;;;eAAAA;;;AAAN,IAAMA,WAAW;IACtBC,QAAQ;IACRC,QAAQ;AACV"}
@@ -1,11 +1,13 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { Box, Static, useApp, useInput, useStdin } from 'ink';
2
+ import { Box, Text, useApp, useInput, useStdin } from 'ink';
3
3
  import { useEffect, useSyncExternalStore } from 'react';
4
+ import { EXPANDED_MAX_VISIBLE_LINES } from '../constants.js';
4
5
  import { processStore } from '../state/processStore.js';
5
6
  import CompactProcessLine from './CompactProcessLine.js';
6
7
  import Divider from './Divider.js';
7
8
  import ErrorDetailModal from './ErrorDetailModal.js';
8
9
  import ErrorListModal from './ErrorListModal.js';
10
+ import ExpandedOutput from './ExpandedOutput.js';
9
11
  import StatusBar from './StatusBar.js';
10
12
  export default function App() {
11
13
  const { exit } = useApp();
@@ -14,15 +16,20 @@ export default function App() {
14
16
  const processes = useSyncExternalStore(processStore.subscribe, processStore.getSnapshot);
15
17
  const shouldExit = useSyncExternalStore(processStore.subscribe, processStore.getShouldExit);
16
18
  const mode = useSyncExternalStore(processStore.subscribe, processStore.getMode);
19
+ const selectedIndex = useSyncExternalStore(processStore.subscribe, processStore.getSelectedIndex);
17
20
  const selectedErrorIndex = useSyncExternalStore(processStore.subscribe, processStore.getSelectedErrorIndex);
21
+ const expandedId = useSyncExternalStore(processStore.subscribe, processStore.getExpandedId);
22
+ const scrollOffset = useSyncExternalStore(processStore.subscribe, processStore.getScrollOffset);
18
23
  // Derived state
19
- const completedProcesses = processStore.getCompletedProcesses();
20
- const runningProcesses = processStore.getRunningProcesses();
21
24
  const failedProcesses = processStore.getFailedProcesses();
22
25
  const runningCount = processStore.getRunningCount();
23
26
  const doneCount = processStore.getDoneCount();
24
27
  const errorCount = processStore.getErrorCount();
25
28
  const errorLineCount = processStore.getErrorLineCount();
29
+ const header = processStore.getHeader();
30
+ const showStatusBar = processStore.getShowStatusBar();
31
+ const isInteractive = processStore.getIsInteractive();
32
+ const isAllComplete = processStore.isAllComplete();
26
33
  // Handle exit signal
27
34
  useEffect(()=>{
28
35
  if (shouldExit) {
@@ -32,15 +39,61 @@ export default function App() {
32
39
  shouldExit,
33
40
  exit
34
41
  ]);
42
+ // Auto-enter interactive mode when all complete and interactive flag is set
43
+ useEffect(()=>{
44
+ if (isAllComplete && isInteractive && mode === 'normal') {
45
+ processStore.setMode('interactive');
46
+ }
47
+ }, [
48
+ isAllComplete,
49
+ isInteractive,
50
+ mode
51
+ ]);
35
52
  // Keyboard handling (only active when raw mode is supported)
36
53
  useInput((input, key)=>{
37
54
  if (mode === 'normal') {
38
55
  if (input === 'e' && errorCount > 0) {
39
56
  processStore.setMode('errorList');
40
57
  }
58
+ } else if (mode === 'interactive') {
59
+ if (input === 'q' || key.escape) {
60
+ if (expandedId) {
61
+ processStore.collapse();
62
+ } else {
63
+ processStore.signalExit(()=>{});
64
+ }
65
+ } else if (key.return) {
66
+ processStore.toggleExpand();
67
+ } else if (key.downArrow) {
68
+ if (expandedId) {
69
+ processStore.scrollDown(EXPANDED_MAX_VISIBLE_LINES);
70
+ } else {
71
+ processStore.selectNext();
72
+ }
73
+ } else if (key.upArrow) {
74
+ if (expandedId) {
75
+ processStore.scrollUp();
76
+ } else {
77
+ processStore.selectPrev();
78
+ }
79
+ } else if (input === 'j') {
80
+ if (expandedId) {
81
+ processStore.scrollDown(EXPANDED_MAX_VISIBLE_LINES);
82
+ } else {
83
+ processStore.selectNext();
84
+ }
85
+ } else if (input === 'k') {
86
+ if (expandedId) {
87
+ processStore.scrollUp();
88
+ } else {
89
+ processStore.selectPrev();
90
+ }
91
+ } else if (input === 'e' && errorCount > 0) {
92
+ processStore.setMode('errorList');
93
+ }
41
94
  } else if (mode === 'errorList') {
42
95
  if (key.escape) {
43
- processStore.setMode('normal');
96
+ processStore.setMode(isInteractive ? 'interactive' : 'normal');
44
97
  } else if (key.downArrow) {
45
98
  processStore.selectNextError();
46
99
  } else if (key.upArrow) {
@@ -81,21 +134,33 @@ export default function App() {
81
134
  // Fallback if no error selected
82
135
  processStore.setMode('errorList');
83
136
  }
84
- // Normal view
137
+ // Normal/Interactive view - render in original registration order
138
+ const showSelection = mode === 'interactive';
85
139
  return /*#__PURE__*/ _jsxs(Box, {
86
140
  flexDirection: "column",
87
141
  children: [
88
- /*#__PURE__*/ _jsx(Static, {
89
- items: completedProcesses,
90
- children: (item)=>/*#__PURE__*/ _jsx(CompactProcessLine, {
91
- item: item
92
- }, item.id)
142
+ header && /*#__PURE__*/ _jsxs(_Fragment, {
143
+ children: [
144
+ /*#__PURE__*/ _jsx(Text, {
145
+ children: header
146
+ }),
147
+ /*#__PURE__*/ _jsx(Divider, {})
148
+ ]
93
149
  }),
94
- completedProcesses.length > 0 && runningProcesses.length > 0 && /*#__PURE__*/ _jsx(Divider, {}),
95
- runningProcesses.map((item)=>/*#__PURE__*/ _jsx(CompactProcessLine, {
96
- item: item
150
+ processes.map((item, index)=>/*#__PURE__*/ _jsxs(Box, {
151
+ flexDirection: "column",
152
+ children: [
153
+ /*#__PURE__*/ _jsx(CompactProcessLine, {
154
+ item: item,
155
+ isSelected: showSelection && index === selectedIndex
156
+ }),
157
+ expandedId === item.id && /*#__PURE__*/ _jsx(ExpandedOutput, {
158
+ lines: item.lines,
159
+ scrollOffset: scrollOffset
160
+ })
161
+ ]
97
162
  }, item.id)),
98
- processes.length > 0 && /*#__PURE__*/ _jsxs(_Fragment, {
163
+ showStatusBar && processes.length > 0 && /*#__PURE__*/ _jsxs(_Fragment, {
99
164
  children: [
100
165
  /*#__PURE__*/ _jsx(Divider, {}),
101
166
  /*#__PURE__*/ _jsx(StatusBar, {
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/components/App.tsx"],"sourcesContent":["import { Box, Static, useApp, useInput, useStdin } from 'ink';\nimport { useEffect, useSyncExternalStore } from 'react';\nimport { processStore } from '../state/processStore.ts';\nimport CompactProcessLine from './CompactProcessLine.ts';\nimport Divider from './Divider.ts';\nimport ErrorDetailModal from './ErrorDetailModal.ts';\nimport ErrorListModal from './ErrorListModal.ts';\nimport StatusBar from './StatusBar.ts';\n\nexport default function App(): React.JSX.Element {\n const { exit } = useApp();\n const { isRawModeSupported } = useStdin();\n\n // Subscribe to store state\n const processes = useSyncExternalStore(processStore.subscribe, processStore.getSnapshot);\n const shouldExit = useSyncExternalStore(processStore.subscribe, processStore.getShouldExit);\n const mode = useSyncExternalStore(processStore.subscribe, processStore.getMode);\n const selectedErrorIndex = useSyncExternalStore(processStore.subscribe, processStore.getSelectedErrorIndex);\n\n // Derived state\n const completedProcesses = processStore.getCompletedProcesses();\n const runningProcesses = processStore.getRunningProcesses();\n const failedProcesses = processStore.getFailedProcesses();\n const runningCount = processStore.getRunningCount();\n const doneCount = processStore.getDoneCount();\n const errorCount = processStore.getErrorCount();\n const errorLineCount = processStore.getErrorLineCount();\n\n // Handle exit signal\n useEffect(() => {\n if (shouldExit) {\n exit();\n }\n }, [shouldExit, exit]);\n\n // Keyboard handling (only active when raw mode is supported)\n useInput(\n (input, key) => {\n if (mode === 'normal') {\n if (input === 'e' && errorCount > 0) {\n processStore.setMode('errorList');\n }\n } else if (mode === 'errorList') {\n if (key.escape) {\n processStore.setMode('normal');\n } else if (key.downArrow) {\n processStore.selectNextError();\n } else if (key.upArrow) {\n processStore.selectPrevError();\n } else if (key.return) {\n processStore.setMode('errorDetail');\n }\n } else if (mode === 'errorDetail') {\n if (key.escape) {\n processStore.setMode('errorList');\n } else if (key.downArrow) {\n processStore.selectNextError();\n } else if (key.upArrow) {\n processStore.selectPrevError();\n }\n }\n },\n { isActive: isRawModeSupported === true }\n );\n\n // Error list modal\n if (mode === 'errorList') {\n return <ErrorListModal errors={failedProcesses} selectedIndex={selectedErrorIndex} totalErrorLines={errorLineCount} />;\n }\n\n // Error detail modal\n if (mode === 'errorDetail') {\n const selectedError = processStore.getSelectedError();\n if (selectedError) {\n return <ErrorDetailModal error={selectedError} currentIndex={selectedErrorIndex} totalErrors={failedProcesses.length} />;\n }\n // Fallback if no error selected\n processStore.setMode('errorList');\n }\n\n // Normal view\n return (\n <Box flexDirection=\"column\">\n {/* Static area - completed processes (completion order) */}\n <Static items={completedProcesses}>{(item) => <CompactProcessLine key={item.id} item={item} />}</Static>\n\n {/* Divider between completed and running */}\n {completedProcesses.length > 0 && runningProcesses.length > 0 && <Divider />}\n\n {/* Dynamic area - running processes */}\n {runningProcesses.map((item) => (\n <CompactProcessLine key={item.id} item={item} />\n ))}\n\n {/* Status bar */}\n {processes.length > 0 && (\n <>\n <Divider />\n <StatusBar running={runningCount} done={doneCount} errors={errorCount} errorLines={errorLineCount} />\n </>\n )}\n </Box>\n );\n}\n"],"names":["Box","Static","useApp","useInput","useStdin","useEffect","useSyncExternalStore","processStore","CompactProcessLine","Divider","ErrorDetailModal","ErrorListModal","StatusBar","App","exit","isRawModeSupported","processes","subscribe","getSnapshot","shouldExit","getShouldExit","mode","getMode","selectedErrorIndex","getSelectedErrorIndex","completedProcesses","getCompletedProcesses","runningProcesses","getRunningProcesses","failedProcesses","getFailedProcesses","runningCount","getRunningCount","doneCount","getDoneCount","errorCount","getErrorCount","errorLineCount","getErrorLineCount","input","key","setMode","escape","downArrow","selectNextError","upArrow","selectPrevError","return","isActive","errors","selectedIndex","totalErrorLines","selectedError","getSelectedError","error","currentIndex","totalErrors","length","flexDirection","items","item","id","map","running","done","errorLines"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/components/App.tsx"],"sourcesContent":["import { Box, Text, useApp, useInput, useStdin } from 'ink';\nimport { useEffect, useSyncExternalStore } from 'react';\nimport { EXPANDED_MAX_VISIBLE_LINES } from '../constants.ts';\nimport { processStore } from '../state/processStore.ts';\nimport CompactProcessLine from './CompactProcessLine.ts';\nimport Divider from './Divider.ts';\nimport ErrorDetailModal from './ErrorDetailModal.ts';\nimport ErrorListModal from './ErrorListModal.ts';\nimport ExpandedOutput from './ExpandedOutput.ts';\nimport StatusBar from './StatusBar.ts';\n\nexport default function App(): React.JSX.Element {\n const { exit } = useApp();\n const { isRawModeSupported } = useStdin();\n\n // Subscribe to store state\n const processes = useSyncExternalStore(processStore.subscribe, processStore.getSnapshot);\n const shouldExit = useSyncExternalStore(processStore.subscribe, processStore.getShouldExit);\n const mode = useSyncExternalStore(processStore.subscribe, processStore.getMode);\n const selectedIndex = useSyncExternalStore(processStore.subscribe, processStore.getSelectedIndex);\n const selectedErrorIndex = useSyncExternalStore(processStore.subscribe, processStore.getSelectedErrorIndex);\n const expandedId = useSyncExternalStore(processStore.subscribe, processStore.getExpandedId);\n const scrollOffset = useSyncExternalStore(processStore.subscribe, processStore.getScrollOffset);\n\n // Derived state\n const failedProcesses = processStore.getFailedProcesses();\n const runningCount = processStore.getRunningCount();\n const doneCount = processStore.getDoneCount();\n const errorCount = processStore.getErrorCount();\n const errorLineCount = processStore.getErrorLineCount();\n const header = processStore.getHeader();\n const showStatusBar = processStore.getShowStatusBar();\n const isInteractive = processStore.getIsInteractive();\n const isAllComplete = processStore.isAllComplete();\n\n // Handle exit signal\n useEffect(() => {\n if (shouldExit) {\n exit();\n }\n }, [shouldExit, exit]);\n\n // Auto-enter interactive mode when all complete and interactive flag is set\n useEffect(() => {\n if (isAllComplete && isInteractive && mode === 'normal') {\n processStore.setMode('interactive');\n }\n }, [isAllComplete, isInteractive, mode]);\n\n // Keyboard handling (only active when raw mode is supported)\n useInput(\n (input, key) => {\n if (mode === 'normal') {\n if (input === 'e' && errorCount > 0) {\n processStore.setMode('errorList');\n }\n } else if (mode === 'interactive') {\n if (input === 'q' || key.escape) {\n if (expandedId) {\n processStore.collapse();\n } else {\n processStore.signalExit(() => {});\n }\n } else if (key.return) {\n processStore.toggleExpand();\n } else if (key.downArrow) {\n if (expandedId) {\n processStore.scrollDown(EXPANDED_MAX_VISIBLE_LINES);\n } else {\n processStore.selectNext();\n }\n } else if (key.upArrow) {\n if (expandedId) {\n processStore.scrollUp();\n } else {\n processStore.selectPrev();\n }\n } else if (input === 'j') {\n if (expandedId) {\n processStore.scrollDown(EXPANDED_MAX_VISIBLE_LINES);\n } else {\n processStore.selectNext();\n }\n } else if (input === 'k') {\n if (expandedId) {\n processStore.scrollUp();\n } else {\n processStore.selectPrev();\n }\n } else if (input === 'e' && errorCount > 0) {\n processStore.setMode('errorList');\n }\n } else if (mode === 'errorList') {\n if (key.escape) {\n processStore.setMode(isInteractive ? 'interactive' : 'normal');\n } else if (key.downArrow) {\n processStore.selectNextError();\n } else if (key.upArrow) {\n processStore.selectPrevError();\n } else if (key.return) {\n processStore.setMode('errorDetail');\n }\n } else if (mode === 'errorDetail') {\n if (key.escape) {\n processStore.setMode('errorList');\n } else if (key.downArrow) {\n processStore.selectNextError();\n } else if (key.upArrow) {\n processStore.selectPrevError();\n }\n }\n },\n { isActive: isRawModeSupported === true }\n );\n\n // Error list modal\n if (mode === 'errorList') {\n return <ErrorListModal errors={failedProcesses} selectedIndex={selectedErrorIndex} totalErrorLines={errorLineCount} />;\n }\n\n // Error detail modal\n if (mode === 'errorDetail') {\n const selectedError = processStore.getSelectedError();\n if (selectedError) {\n return <ErrorDetailModal error={selectedError} currentIndex={selectedErrorIndex} totalErrors={failedProcesses.length} />;\n }\n // Fallback if no error selected\n processStore.setMode('errorList');\n }\n\n // Normal/Interactive view - render in original registration order\n const showSelection = mode === 'interactive';\n return (\n <Box flexDirection=\"column\">\n {/* Header */}\n {header && (\n <>\n <Text>{header}</Text>\n <Divider />\n </>\n )}\n\n {/* All processes in registration order */}\n {processes.map((item, index) => (\n <Box key={item.id} flexDirection=\"column\">\n <CompactProcessLine item={item} isSelected={showSelection && index === selectedIndex} />\n {expandedId === item.id && <ExpandedOutput lines={item.lines} scrollOffset={scrollOffset} />}\n </Box>\n ))}\n\n {/* Status bar */}\n {showStatusBar && processes.length > 0 && (\n <>\n <Divider />\n <StatusBar running={runningCount} done={doneCount} errors={errorCount} errorLines={errorLineCount} />\n </>\n )}\n </Box>\n );\n}\n"],"names":["Box","Text","useApp","useInput","useStdin","useEffect","useSyncExternalStore","EXPANDED_MAX_VISIBLE_LINES","processStore","CompactProcessLine","Divider","ErrorDetailModal","ErrorListModal","ExpandedOutput","StatusBar","App","exit","isRawModeSupported","processes","subscribe","getSnapshot","shouldExit","getShouldExit","mode","getMode","selectedIndex","getSelectedIndex","selectedErrorIndex","getSelectedErrorIndex","expandedId","getExpandedId","scrollOffset","getScrollOffset","failedProcesses","getFailedProcesses","runningCount","getRunningCount","doneCount","getDoneCount","errorCount","getErrorCount","errorLineCount","getErrorLineCount","header","getHeader","showStatusBar","getShowStatusBar","isInteractive","getIsInteractive","isAllComplete","setMode","input","key","escape","collapse","signalExit","return","toggleExpand","downArrow","scrollDown","selectNext","upArrow","scrollUp","selectPrev","selectNextError","selectPrevError","isActive","errors","totalErrorLines","selectedError","getSelectedError","error","currentIndex","totalErrors","length","showSelection","flexDirection","map","item","index","isSelected","id","lines","running","done","errorLines"],"mappings":"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"}
@@ -30,6 +30,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
30
30
  import { Box, Text, useStdout } from 'ink';
31
31
  import { memo, useMemo } from 'react';
32
32
  import figures from '../lib/figures.js';
33
+ import { calculateColumnWidth } from '../lib/format.js';
34
+ import { processStore } from '../state/processStore.js';
33
35
  import { LineType } from '../types.js';
34
36
  import Spinner from './Spinner.js';
35
37
  // From: https://github.com/sindresorhus/cli-spinners/blob/00de8fbeee16fa49502fa4f687449f70f2c8ca2c/spinners.json#L2
@@ -63,15 +65,17 @@ function getLastOutputLine(lines) {
63
65
  function getErrorCount(lines) {
64
66
  return lines.filter((line)=>line.type === LineType.stderr).length;
65
67
  }
66
- export default /*#__PURE__*/ memo(function CompactProcessLine({ item }) {
68
+ export default /*#__PURE__*/ memo(function CompactProcessLine({ item, isSelected = false }) {
67
69
  const { stdout } = useStdout();
68
70
  const terminalWidth = (stdout === null || stdout === void 0 ? void 0 : stdout.columns) || 80;
69
71
  const { group, title, state, lines } = item;
72
+ const selectionIndicator = isSelected ? figures.pointer : ' ';
70
73
  // Display name: prefer group, fall back to title
71
74
  const displayName = group || title;
72
- // Calculate widths - use fixed-width columns for alignment
75
+ // Calculate widths - use dynamic column width based on longest name
73
76
  const iconWidth = 2; // icon + space
74
- const nameColumnWidth = 15; // fixed width for name column
77
+ const maxGroupLength = processStore.getMaxGroupLength();
78
+ const nameColumnWidth = calculateColumnWidth('max', terminalWidth, maxGroupLength);
75
79
  const gap = 1; // space between name and status
76
80
  const statusWidth = terminalWidth - iconWidth - nameColumnWidth - gap;
77
81
  // Truncate name if needed and pad to column width
@@ -115,11 +119,16 @@ export default /*#__PURE__*/ memo(function CompactProcessLine({ item }) {
115
119
  const statusColor = state === 'error' ? 'red' : 'gray';
116
120
  return /*#__PURE__*/ _jsxs(Box, {
117
121
  children: [
122
+ /*#__PURE__*/ _jsx(Text, {
123
+ color: isSelected ? 'cyan' : undefined,
124
+ children: selectionIndicator
125
+ }),
118
126
  /*#__PURE__*/ _jsx(Box, {
119
127
  width: iconWidth,
120
128
  children: icon
121
129
  }),
122
130
  /*#__PURE__*/ _jsx(Text, {
131
+ inverse: isSelected,
123
132
  children: truncatedName
124
133
  }),
125
134
  statusText && /*#__PURE__*/ _jsxs(Text, {
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/components/CompactProcessLine.tsx"],"sourcesContent":["import { Box, Text, useStdout } from 'ink';\nimport { memo, useMemo } from 'react';\nimport figures from '../lib/figures.ts';\nimport type { ChildProcess, Line } from '../types.ts';\nimport { LineType } from '../types.ts';\nimport Spinner from './Spinner.ts';\n\n// From: https://github.com/sindresorhus/cli-spinners/blob/00de8fbeee16fa49502fa4f687449f70f2c8ca2c/spinners.json#L2\nconst SPINNER = {\n interval: 80,\n frames: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'],\n};\n\ntype Props = {\n item: ChildProcess;\n};\n\nfunction truncate(str: string, maxLength: number): string {\n if (str.length <= maxLength) return str;\n return `${str.slice(0, maxLength - 1)}…`;\n}\n\nfunction getLastOutputLine(lines: Line[]): string {\n for (let i = lines.length - 1; i >= 0; i--) {\n if (lines[i].text.length > 0) {\n return lines[i].text;\n }\n }\n return '';\n}\n\nfunction getErrorCount(lines: Line[]): number {\n return lines.filter((line) => line.type === LineType.stderr).length;\n}\n\nexport default memo(function CompactProcessLine({ item }: Props) {\n const { stdout } = useStdout();\n const terminalWidth = stdout?.columns || 80;\n\n const { group, title, state, lines } = item;\n\n // Display name: prefer group, fall back to title\n const displayName = group || title;\n\n // Calculate widths - use fixed-width columns for alignment\n const iconWidth = 2; // icon + space\n const nameColumnWidth = 15; // fixed width for name column\n const gap = 1; // space between name and status\n const statusWidth = terminalWidth - iconWidth - nameColumnWidth - gap;\n\n // Truncate name if needed and pad to column width\n const truncatedName = truncate(displayName, nameColumnWidth).padEnd(nameColumnWidth);\n\n // Status text based on state\n const statusText = useMemo(() => {\n if (state === 'running') {\n const lastLine = getLastOutputLine(lines);\n return lastLine ? truncate(lastLine, statusWidth) : '';\n }\n if (state === 'error') {\n const errorCount = getErrorCount(lines);\n return errorCount > 0 ? `${errorCount} error${errorCount > 1 ? 's' : ''}` : 'failed';\n }\n return ''; // success - no status text\n }, [state, lines, statusWidth]);\n\n // Icon based on state\n const icon = useMemo(() => {\n switch (state) {\n case 'running':\n return <Spinner {...SPINNER} />;\n case 'success':\n return <Text color=\"green\">{figures.tick}</Text>;\n case 'error':\n return <Text color=\"red\">{figures.cross}</Text>;\n }\n }, [state]);\n\n // Status text color\n const statusColor = state === 'error' ? 'red' : 'gray';\n\n return (\n <Box>\n <Box width={iconWidth}>{icon}</Box>\n <Text>{truncatedName}</Text>\n {statusText && <Text color={statusColor}> {statusText}</Text>}\n </Box>\n );\n});\n"],"names":["Box","Text","useStdout","memo","useMemo","figures","LineType","Spinner","SPINNER","interval","frames","truncate","str","maxLength","length","slice","getLastOutputLine","lines","i","text","getErrorCount","filter","line","type","stderr","CompactProcessLine","item","stdout","terminalWidth","columns","group","title","state","displayName","iconWidth","nameColumnWidth","gap","statusWidth","truncatedName","padEnd","statusText","lastLine","errorCount","icon","color","tick","cross","statusColor","width"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAASA,GAAG,EAAEC,IAAI,EAAEC,SAAS,QAAQ,MAAM;AAC3C,SAASC,IAAI,EAAEC,OAAO,QAAQ,QAAQ;AACtC,OAAOC,aAAa,oBAAoB;AAExC,SAASC,QAAQ,QAAQ,cAAc;AACvC,OAAOC,aAAa,eAAe;AAEnC,oHAAoH;AACpH,MAAMC,UAAU;IACdC,UAAU;IACVC,QAAQ;QAAC;QAAK;QAAK;QAAK;QAAK;QAAK;QAAK;QAAK;QAAK;QAAK;KAAI;AAC5D;AAMA,SAASC,SAASC,GAAW,EAAEC,SAAiB;IAC9C,IAAID,IAAIE,MAAM,IAAID,WAAW,OAAOD;IACpC,OAAO,GAAGA,IAAIG,KAAK,CAAC,GAAGF,YAAY,GAAG,CAAC,CAAC;AAC1C;AAEA,SAASG,kBAAkBC,KAAa;IACtC,IAAK,IAAIC,IAAID,MAAMH,MAAM,GAAG,GAAGI,KAAK,GAAGA,IAAK;QAC1C,IAAID,KAAK,CAACC,EAAE,CAACC,IAAI,CAACL,MAAM,GAAG,GAAG;YAC5B,OAAOG,KAAK,CAACC,EAAE,CAACC,IAAI;QACtB;IACF;IACA,OAAO;AACT;AAEA,SAASC,cAAcH,KAAa;IAClC,OAAOA,MAAMI,MAAM,CAAC,CAACC,OAASA,KAAKC,IAAI,KAAKjB,SAASkB,MAAM,EAAEV,MAAM;AACrE;AAEA,6BAAeX,KAAK,SAASsB,mBAAmB,EAAEC,IAAI,EAAS;IAC7D,MAAM,EAAEC,MAAM,EAAE,GAAGzB;IACnB,MAAM0B,gBAAgBD,CAAAA,mBAAAA,6BAAAA,OAAQE,OAAO,KAAI;IAEzC,MAAM,EAAEC,KAAK,EAAEC,KAAK,EAAEC,KAAK,EAAEf,KAAK,EAAE,GAAGS;IAEvC,iDAAiD;IACjD,MAAMO,cAAcH,SAASC;IAE7B,2DAA2D;IAC3D,MAAMG,YAAY,GAAG,eAAe;IACpC,MAAMC,kBAAkB,IAAI,8BAA8B;IAC1D,MAAMC,MAAM,GAAG,gCAAgC;IAC/C,MAAMC,cAAcT,gBAAgBM,YAAYC,kBAAkBC;IAElE,kDAAkD;IAClD,MAAME,gBAAgB3B,SAASsB,aAAaE,iBAAiBI,MAAM,CAACJ;IAEpE,6BAA6B;IAC7B,MAAMK,aAAapC,QAAQ;QACzB,IAAI4B,UAAU,WAAW;YACvB,MAAMS,WAAWzB,kBAAkBC;YACnC,OAAOwB,WAAW9B,SAAS8B,UAAUJ,eAAe;QACtD;QACA,IAAIL,UAAU,SAAS;YACrB,MAAMU,aAAatB,cAAcH;YACjC,OAAOyB,aAAa,IAAI,GAAGA,WAAW,MAAM,EAAEA,aAAa,IAAI,MAAM,IAAI,GAAG;QAC9E;QACA,OAAO,IAAI,2BAA2B;IACxC,GAAG;QAACV;QAAOf;QAAOoB;KAAY;IAE9B,sBAAsB;IACtB,MAAMM,OAAOvC,QAAQ;QACnB,OAAQ4B;YACN,KAAK;gBACH,qBAAO,KAACzB,4BAAYC;YACtB,KAAK;gBACH,qBAAO,KAACP;oBAAK2C,OAAM;8BAASvC,QAAQwC,IAAI;;YAC1C,KAAK;gBACH,qBAAO,KAAC5C;oBAAK2C,OAAM;8BAAOvC,QAAQyC,KAAK;;QAC3C;IACF,GAAG;QAACd;KAAM;IAEV,oBAAoB;IACpB,MAAMe,cAAcf,UAAU,UAAU,QAAQ;IAEhD,qBACE,MAAChC;;0BACC,KAACA;gBAAIgD,OAAOd;0BAAYS;;0BACxB,KAAC1C;0BAAMqC;;YACNE,4BAAc,MAACvC;gBAAK2C,OAAOG;;oBAAa;oBAAEP;;;;;AAGjD,GAAG"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/components/CompactProcessLine.tsx"],"sourcesContent":["import { Box, Text, useStdout } from 'ink';\nimport { memo, useMemo } from 'react';\nimport figures from '../lib/figures.ts';\nimport { calculateColumnWidth } from '../lib/format.ts';\nimport { processStore } from '../state/processStore.ts';\nimport type { ChildProcess, Line } from '../types.ts';\nimport { LineType } from '../types.ts';\nimport Spinner from './Spinner.ts';\n\n// From: https://github.com/sindresorhus/cli-spinners/blob/00de8fbeee16fa49502fa4f687449f70f2c8ca2c/spinners.json#L2\nconst SPINNER = {\n interval: 80,\n frames: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'],\n};\n\ntype Props = {\n item: ChildProcess;\n isSelected?: boolean;\n};\n\nfunction truncate(str: string, maxLength: number): string {\n if (str.length <= maxLength) return str;\n return `${str.slice(0, maxLength - 1)}…`;\n}\n\nfunction getLastOutputLine(lines: Line[]): string {\n for (let i = lines.length - 1; i >= 0; i--) {\n if (lines[i].text.length > 0) {\n return lines[i].text;\n }\n }\n return '';\n}\n\nfunction getErrorCount(lines: Line[]): number {\n return lines.filter((line) => line.type === LineType.stderr).length;\n}\n\nexport default memo(function CompactProcessLine({ item, isSelected = false }: Props) {\n const { stdout } = useStdout();\n const terminalWidth = stdout?.columns || 80;\n\n const { group, title, state, lines } = item;\n const selectionIndicator = isSelected ? figures.pointer : ' ';\n\n // Display name: prefer group, fall back to title\n const displayName = group || title;\n\n // Calculate widths - use dynamic column width based on longest name\n const iconWidth = 2; // icon + space\n const maxGroupLength = processStore.getMaxGroupLength();\n const nameColumnWidth = calculateColumnWidth('max', terminalWidth, maxGroupLength);\n const gap = 1; // space between name and status\n const statusWidth = terminalWidth - iconWidth - nameColumnWidth - gap;\n\n // Truncate name if needed and pad to column width\n const truncatedName = truncate(displayName, nameColumnWidth).padEnd(nameColumnWidth);\n\n // Status text based on state\n const statusText = useMemo(() => {\n if (state === 'running') {\n const lastLine = getLastOutputLine(lines);\n return lastLine ? truncate(lastLine, statusWidth) : '';\n }\n if (state === 'error') {\n const errorCount = getErrorCount(lines);\n return errorCount > 0 ? `${errorCount} error${errorCount > 1 ? 's' : ''}` : 'failed';\n }\n return ''; // success - no status text\n }, [state, lines, statusWidth]);\n\n // Icon based on state\n const icon = useMemo(() => {\n switch (state) {\n case 'running':\n return <Spinner {...SPINNER} />;\n case 'success':\n return <Text color=\"green\">{figures.tick}</Text>;\n case 'error':\n return <Text color=\"red\">{figures.cross}</Text>;\n }\n }, [state]);\n\n // Status text color\n const statusColor = state === 'error' ? 'red' : 'gray';\n\n return (\n <Box>\n <Text color={isSelected ? 'cyan' : undefined}>{selectionIndicator}</Text>\n <Box width={iconWidth}>{icon}</Box>\n <Text inverse={isSelected}>{truncatedName}</Text>\n {statusText && <Text color={statusColor}> {statusText}</Text>}\n </Box>\n );\n});\n"],"names":["Box","Text","useStdout","memo","useMemo","figures","calculateColumnWidth","processStore","LineType","Spinner","SPINNER","interval","frames","truncate","str","maxLength","length","slice","getLastOutputLine","lines","i","text","getErrorCount","filter","line","type","stderr","CompactProcessLine","item","isSelected","stdout","terminalWidth","columns","group","title","state","selectionIndicator","pointer","displayName","iconWidth","maxGroupLength","getMaxGroupLength","nameColumnWidth","gap","statusWidth","truncatedName","padEnd","statusText","lastLine","errorCount","icon","color","tick","cross","statusColor","undefined","width","inverse"],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { memo } from 'react';
4
+ import { EXPANDED_MAX_VISIBLE_LINES } from '../constants.js';
5
+ import { LineType } from '../types.js';
6
+ export default /*#__PURE__*/ memo(function ExpandedOutput({ lines, scrollOffset, maxVisible = EXPANDED_MAX_VISIBLE_LINES }) {
7
+ const visibleLines = lines.slice(scrollOffset, scrollOffset + maxVisible);
8
+ const hasMore = lines.length > scrollOffset + maxVisible;
9
+ const remaining = lines.length - scrollOffset - maxVisible;
10
+ if (lines.length === 0) {
11
+ return /*#__PURE__*/ _jsx(Box, {
12
+ paddingLeft: 2,
13
+ children: /*#__PURE__*/ _jsx(Text, {
14
+ dimColor: true,
15
+ children: "│ (no output)"
16
+ })
17
+ });
18
+ }
19
+ return /*#__PURE__*/ _jsxs(Box, {
20
+ flexDirection: "column",
21
+ paddingLeft: 2,
22
+ children: [
23
+ visibleLines.map((line, i)=>// biome-ignore lint/suspicious/noArrayIndexKey: Lines have no unique ID, index is stable for this scrolling view
24
+ /*#__PURE__*/ _jsxs(Text, {
25
+ color: line.type === LineType.stderr ? 'red' : undefined,
26
+ children: [
27
+ "│ ",
28
+ line.text
29
+ ]
30
+ }, scrollOffset + i)),
31
+ hasMore && /*#__PURE__*/ _jsxs(Text, {
32
+ dimColor: true,
33
+ children: [
34
+ "│ [+",
35
+ remaining,
36
+ " more, j/k to scroll]"
37
+ ]
38
+ })
39
+ ]
40
+ });
41
+ });
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/components/ExpandedOutput.tsx"],"sourcesContent":["import { Box, Text } from 'ink';\nimport { memo } from 'react';\nimport { EXPANDED_MAX_VISIBLE_LINES } from '../constants.ts';\nimport type { Line } from '../types.ts';\nimport { LineType } from '../types.ts';\n\ntype Props = {\n lines: Line[];\n scrollOffset: number;\n maxVisible?: number;\n};\n\nexport default memo(function ExpandedOutput({ lines, scrollOffset, maxVisible = EXPANDED_MAX_VISIBLE_LINES }: Props) {\n const visibleLines = lines.slice(scrollOffset, scrollOffset + maxVisible);\n const hasMore = lines.length > scrollOffset + maxVisible;\n const remaining = lines.length - scrollOffset - maxVisible;\n\n if (lines.length === 0) {\n return (\n <Box paddingLeft={2}>\n <Text dimColor>│ (no output)</Text>\n </Box>\n );\n }\n\n return (\n <Box flexDirection=\"column\" paddingLeft={2}>\n {visibleLines.map((line, i) => (\n // biome-ignore lint/suspicious/noArrayIndexKey: Lines have no unique ID, index is stable for this scrolling view\n <Text key={scrollOffset + i} color={line.type === LineType.stderr ? 'red' : undefined}>\n │ {line.text}\n </Text>\n ))}\n {hasMore && <Text dimColor>│ [+{remaining} more, j/k to scroll]</Text>}\n </Box>\n );\n});\n"],"names":["Box","Text","memo","EXPANDED_MAX_VISIBLE_LINES","LineType","ExpandedOutput","lines","scrollOffset","maxVisible","visibleLines","slice","hasMore","length","remaining","paddingLeft","dimColor","flexDirection","map","line","i","color","type","stderr","undefined","text"],"mappings":";AAAA,SAASA,GAAG,EAAEC,IAAI,QAAQ,MAAM;AAChC,SAASC,IAAI,QAAQ,QAAQ;AAC7B,SAASC,0BAA0B,QAAQ,kBAAkB;AAE7D,SAASC,QAAQ,QAAQ,cAAc;AAQvC,6BAAeF,KAAK,SAASG,eAAe,EAAEC,KAAK,EAAEC,YAAY,EAAEC,aAAaL,0BAA0B,EAAS;IACjH,MAAMM,eAAeH,MAAMI,KAAK,CAACH,cAAcA,eAAeC;IAC9D,MAAMG,UAAUL,MAAMM,MAAM,GAAGL,eAAeC;IAC9C,MAAMK,YAAYP,MAAMM,MAAM,GAAGL,eAAeC;IAEhD,IAAIF,MAAMM,MAAM,KAAK,GAAG;QACtB,qBACE,KAACZ;YAAIc,aAAa;sBAChB,cAAA,KAACb;gBAAKc,QAAQ;0BAAC;;;IAGrB;IAEA,qBACE,MAACf;QAAIgB,eAAc;QAASF,aAAa;;YACtCL,aAAaQ,GAAG,CAAC,CAACC,MAAMC,IACvB,iHAAiH;8BACjH,MAAClB;oBAA4BmB,OAAOF,KAAKG,IAAI,KAAKjB,SAASkB,MAAM,GAAG,QAAQC;;wBAAW;wBAClFL,KAAKM,IAAI;;mBADHjB,eAAeY;YAI3BR,yBAAW,MAACV;gBAAKc,QAAQ;;oBAAC;oBAAKF;oBAAU;;;;;AAGhD,GAAG"}