spawn-term 3.0.4 → 3.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/components/App.js +10 -47
- package/dist/cjs/components/App.js.map +1 -1
- package/dist/cjs/components/ErrorFooter.js +120 -0
- package/dist/cjs/components/ErrorFooter.js.map +1 -0
- package/dist/cjs/components/ExpandedOutput.js +0 -2
- package/dist/cjs/components/ExpandedOutput.js.map +1 -1
- package/dist/cjs/components/StatusBar.js +22 -33
- package/dist/cjs/components/StatusBar.js.map +1 -1
- package/dist/cjs/src/components/ErrorFooter.d.ts +11 -0
- package/dist/cjs/src/state/processStore.d.ts +9 -6
- package/dist/cjs/state/processStore.js +23 -20
- package/dist/cjs/state/processStore.js.map +1 -1
- package/dist/esm/components/App.js +10 -47
- package/dist/esm/components/App.js.map +1 -1
- package/dist/esm/components/ErrorFooter.js +95 -0
- package/dist/esm/components/ErrorFooter.js.map +1 -0
- package/dist/esm/components/ExpandedOutput.js +0 -2
- package/dist/esm/components/ExpandedOutput.js.map +1 -1
- package/dist/esm/components/StatusBar.js +22 -33
- package/dist/esm/components/StatusBar.js.map +1 -1
- package/dist/esm/src/components/ErrorFooter.d.ts +11 -0
- package/dist/esm/src/state/processStore.d.ts +9 -6
- package/dist/esm/state/processStore.js +19 -18
- package/dist/esm/state/processStore.js.map +1 -1
- package/package.json +1 -1
- package/dist/cjs/components/ErrorDetailModal.js +0 -115
- package/dist/cjs/components/ErrorDetailModal.js.map +0 -1
- package/dist/cjs/components/ErrorListModal.js +0 -135
- package/dist/cjs/components/ErrorListModal.js.map +0 -1
- package/dist/cjs/src/components/ErrorDetailModal.d.ts +0 -8
- package/dist/cjs/src/components/ErrorListModal.d.ts +0 -8
- package/dist/esm/components/ErrorDetailModal.js +0 -99
- package/dist/esm/components/ErrorDetailModal.js.map +0 -1
- package/dist/esm/components/ErrorListModal.js +0 -116
- package/dist/esm/components/ErrorListModal.js.map +0 -1
- package/dist/esm/src/components/ErrorDetailModal.d.ts +0 -8
- package/dist/esm/src/components/ErrorListModal.d.ts +0 -8
|
@@ -1 +1 @@
|
|
|
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, SessionOptions } from '../types.ts';\nimport { LineType } from '../types.ts';\n\ntype Listener = () => void;\ntype Mode = 'normal' | 'interactive' | 'errorList' | 'errorDetail';\n\nexport class 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 private listScrollOffset = 0; // Viewport offset for process list\n\n // Session-level display settings (set once at session creation)\n private header: string | undefined;\n private showStatusBar = false;\n private isInteractive = false;\n\n constructor(options: SessionOptions = {}) {\n this.header = options.header;\n this.showStatusBar = options.showStatusBar ?? false;\n this.isInteractive = options.interactive ?? false;\n }\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 getListScrollOffset = (): number => this.listScrollOffset;\n // Session-level getters (set at session creation, immutable)\n getHeader = (): string | undefined => this.header;\n getShowStatusBar = (): boolean => this.showStatusBar;\n getIsInteractive = (): boolean => this.isInteractive;\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 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(visibleCount?: number): void {\n if (this.processes.length > 0) {\n this.selectedIndex = (this.selectedIndex + 1) % this.processes.length;\n this.adjustListScroll(visibleCount);\n this.notify();\n }\n }\n\n selectPrev(visibleCount?: number): void {\n if (this.processes.length > 0) {\n this.selectedIndex = (this.selectedIndex - 1 + this.processes.length) % this.processes.length;\n this.adjustListScroll(visibleCount);\n this.notify();\n }\n }\n\n private adjustListScroll(visibleCount?: number): void {\n if (!visibleCount || visibleCount <= 0) return;\n\n // Ensure selected item is visible in viewport\n if (this.selectedIndex < this.listScrollOffset) {\n // Selected is above viewport - scroll up\n this.listScrollOffset = this.selectedIndex;\n } else if (this.selectedIndex >= this.listScrollOffset + visibleCount) {\n // Selected is below viewport - scroll down\n this.listScrollOffset = this.selectedIndex - visibleCount + 1;\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.listScrollOffset = 0;\n this.header = undefined;\n }\n\n private notify(): void {\n this.listeners.forEach((l) => {\n l();\n });\n }\n}\n\n// Note: No global singleton - session creates its own store instance\n"],"names":["ProcessStore","options","processes","completedIds","listeners","Set","shouldExit","exitCallback","mode","selectedIndex","selectedErrorIndex","expandedId","scrollOffset","listScrollOffset","showStatusBar","isInteractive","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","getListScrollOffset","getHeader","header","getShowStatusBar","getIsInteractive","isAllComplete","every","getShouldExit","getExitCallback","interactive","addProcess","process","notify","updateProcess","update","oldProcess","wasRunning","isNowComplete","includes","appendLines","newLines","concat","getProcess","setMode","selectNext","visibleCount","adjustListScroll","selectPrev","getSelectedProcess","selectNextError","failed","selectPrevError","getSelectedError","toggleExpand","selected","collapse","scrollDown","maxVisible","maxOffset","scrollUp","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, SessionOptions } from '../types.ts';\nimport { LineType } from '../types.ts';\n\ntype Listener = () => void;\ntype Mode = 'normal' | 'interactive';\n\nexport class 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 expandedId: string | null = null;\n private scrollOffset = 0;\n private listScrollOffset = 0; // Viewport offset for process list\n private errorFooterExpanded = false; // For non-interactive error footer\n\n // Session-level display settings (set once at session creation)\n private header: string | undefined;\n private showStatusBar = false;\n private isInteractive = false;\n\n constructor(options: SessionOptions = {}) {\n this.header = options.header;\n this.showStatusBar = options.showStatusBar ?? false;\n this.isInteractive = options.interactive ?? false;\n }\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 getExpandedId = (): string | null => this.expandedId;\n getScrollOffset = (): number => this.scrollOffset;\n getListScrollOffset = (): number => this.listScrollOffset;\n getErrorFooterExpanded = (): boolean => this.errorFooterExpanded;\n // Session-level getters (set at session creation, immutable)\n getHeader = (): string | undefined => this.header;\n getShowStatusBar = (): boolean => this.showStatusBar;\n getIsInteractive = (): boolean => this.isInteractive;\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 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 // Auto-expand error footer when all complete with errors (non-interactive only)\n if (!this.isInteractive && this.isAllComplete() && this.getErrorCount() > 0) {\n this.errorFooterExpanded = true;\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 }\n this.notify();\n }\n\n // Interactive mode navigation\n selectNext(visibleCount?: number): void {\n if (this.processes.length > 0) {\n this.selectedIndex = (this.selectedIndex + 1) % this.processes.length;\n this.adjustListScroll(visibleCount);\n this.notify();\n }\n }\n\n selectPrev(visibleCount?: number): void {\n if (this.processes.length > 0) {\n this.selectedIndex = (this.selectedIndex - 1 + this.processes.length) % this.processes.length;\n this.adjustListScroll(visibleCount);\n this.notify();\n }\n }\n\n private adjustListScroll(visibleCount?: number): void {\n if (!visibleCount || visibleCount <= 0) return;\n\n // Ensure selected item is visible in viewport\n if (this.selectedIndex < this.listScrollOffset) {\n // Selected is above viewport - scroll up\n this.listScrollOffset = this.selectedIndex;\n } else if (this.selectedIndex >= this.listScrollOffset + visibleCount) {\n // Selected is below viewport - scroll down\n this.listScrollOffset = this.selectedIndex - visibleCount + 1;\n }\n }\n\n getSelectedProcess(): ChildProcess | undefined {\n return this.processes[this.selectedIndex];\n }\n\n // Error footer methods (for non-interactive mode)\n toggleErrorFooter(): void {\n this.errorFooterExpanded = !this.errorFooterExpanded;\n this.notify();\n }\n\n expandErrorFooter(): void {\n if (!this.errorFooterExpanded) {\n this.errorFooterExpanded = true;\n this.notify();\n }\n }\n\n getErrorLines(): Array<{ processName: string; lines: Line[] }> {\n return this.getFailedProcesses().map((p) => ({\n processName: p.group || p.title,\n lines: p.lines,\n }));\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.expandedId = null;\n this.scrollOffset = 0;\n this.listScrollOffset = 0;\n this.errorFooterExpanded = false;\n this.header = undefined;\n }\n\n private notify(): void {\n this.listeners.forEach((l) => {\n l();\n });\n }\n}\n\n// Note: No global singleton - session creates its own store instance\n"],"names":["ProcessStore","options","processes","completedIds","listeners","Set","shouldExit","exitCallback","mode","selectedIndex","expandedId","scrollOffset","listScrollOffset","errorFooterExpanded","showStatusBar","isInteractive","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","getExpandedId","getScrollOffset","getListScrollOffset","getErrorFooterExpanded","getHeader","header","getShowStatusBar","getIsInteractive","isAllComplete","every","getShouldExit","getExitCallback","interactive","addProcess","process","notify","updateProcess","update","oldProcess","wasRunning","isNowComplete","includes","appendLines","newLines","concat","getProcess","setMode","selectNext","visibleCount","adjustListScroll","selectPrev","getSelectedProcess","toggleErrorFooter","expandErrorFooter","getErrorLines","processName","toggleExpand","selected","collapse","scrollDown","maxVisible","maxOffset","scrollUp","signalExit","callback","reset","forEach"],"mappings":"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"}
|
|
@@ -5,8 +5,7 @@ import { EXPANDED_MAX_VISIBLE_LINES } from '../constants.js';
|
|
|
5
5
|
import { StoreContext } from '../state/StoreContext.js';
|
|
6
6
|
import CompactProcessLine from './CompactProcessLine.js';
|
|
7
7
|
import Divider from './Divider.js';
|
|
8
|
-
import
|
|
9
|
-
import ErrorListModal from './ErrorListModal.js';
|
|
8
|
+
import ErrorFooter from './ErrorFooter.js';
|
|
10
9
|
import ExpandedOutput from './ExpandedOutput.js';
|
|
11
10
|
import StatusBar from './StatusBar.js';
|
|
12
11
|
function AppContent({ store }) {
|
|
@@ -19,10 +18,10 @@ function AppContent({ store }) {
|
|
|
19
18
|
const shouldExit = useSyncExternalStore(store.subscribe, store.getShouldExit);
|
|
20
19
|
const mode = useSyncExternalStore(store.subscribe, store.getMode);
|
|
21
20
|
const selectedIndex = useSyncExternalStore(store.subscribe, store.getSelectedIndex);
|
|
22
|
-
const selectedErrorIndex = useSyncExternalStore(store.subscribe, store.getSelectedErrorIndex);
|
|
23
21
|
const expandedId = useSyncExternalStore(store.subscribe, store.getExpandedId);
|
|
24
22
|
const scrollOffset = useSyncExternalStore(store.subscribe, store.getScrollOffset);
|
|
25
23
|
const listScrollOffset = useSyncExternalStore(store.subscribe, store.getListScrollOffset);
|
|
24
|
+
const errorFooterExpanded = useSyncExternalStore(store.subscribe, store.getErrorFooterExpanded);
|
|
26
25
|
// Subscribed state that triggers re-renders
|
|
27
26
|
const header = useSyncExternalStore(store.subscribe, store.getHeader);
|
|
28
27
|
const showStatusBar = useSyncExternalStore(store.subscribe, store.getShowStatusBar);
|
|
@@ -31,12 +30,12 @@ function AppContent({ store }) {
|
|
|
31
30
|
const reservedLines = (header ? 2 : 0) + (showStatusBar ? 2 : 0);
|
|
32
31
|
const visibleProcessCount = Math.max(1, terminalHeight - reservedLines);
|
|
33
32
|
// Derived state (computed from processes which is already subscribed)
|
|
34
|
-
const failedProcesses = store.getFailedProcesses();
|
|
35
33
|
const runningCount = store.getRunningCount();
|
|
36
34
|
const doneCount = store.getDoneCount();
|
|
37
35
|
const errorCount = store.getErrorCount();
|
|
38
36
|
const errorLineCount = store.getErrorLineCount();
|
|
39
37
|
const isAllComplete = store.isAllComplete();
|
|
38
|
+
const errorLines = store.getErrorLines();
|
|
40
39
|
// Handle exit signal
|
|
41
40
|
useEffect(()=>{
|
|
42
41
|
if (shouldExit) {
|
|
@@ -60,8 +59,9 @@ function AppContent({ store }) {
|
|
|
60
59
|
// Keyboard handling (only active when raw mode is supported)
|
|
61
60
|
useInput((input, key)=>{
|
|
62
61
|
if (mode === 'normal') {
|
|
62
|
+
// In non-interactive mode, 'e' toggles error footer
|
|
63
63
|
if (input === 'e' && errorCount > 0) {
|
|
64
|
-
store.
|
|
64
|
+
store.toggleErrorFooter();
|
|
65
65
|
}
|
|
66
66
|
} else if (mode === 'interactive') {
|
|
67
67
|
if (input === 'q' || key.escape) {
|
|
@@ -96,32 +96,12 @@ function AppContent({ store }) {
|
|
|
96
96
|
} else {
|
|
97
97
|
store.selectPrev(visibleProcessCount);
|
|
98
98
|
}
|
|
99
|
-
} else if (input === 'e' && errorCount > 0) {
|
|
100
|
-
store.setMode('errorList');
|
|
101
|
-
}
|
|
102
|
-
} else if (mode === 'errorList') {
|
|
103
|
-
if (key.escape) {
|
|
104
|
-
store.setMode(isInteractive ? 'interactive' : 'normal');
|
|
105
|
-
} else if (key.downArrow) {
|
|
106
|
-
store.selectNextError();
|
|
107
|
-
} else if (key.upArrow) {
|
|
108
|
-
store.selectPrevError();
|
|
109
|
-
} else if (key.return) {
|
|
110
|
-
store.setMode('errorDetail');
|
|
111
|
-
}
|
|
112
|
-
} else if (mode === 'errorDetail') {
|
|
113
|
-
if (key.escape) {
|
|
114
|
-
store.setMode('errorList');
|
|
115
|
-
} else if (key.downArrow) {
|
|
116
|
-
store.selectNextError();
|
|
117
|
-
} else if (key.upArrow) {
|
|
118
|
-
store.selectPrevError();
|
|
119
99
|
}
|
|
120
100
|
}
|
|
121
101
|
}, {
|
|
122
102
|
isActive: isRawModeSupported === true
|
|
123
103
|
});
|
|
124
|
-
// Slice processes to visible viewport in interactive mode
|
|
104
|
+
// Slice processes to visible viewport in interactive mode
|
|
125
105
|
const visibleProcesses = useMemo(()=>{
|
|
126
106
|
if (mode === 'interactive') {
|
|
127
107
|
return processes.slice(listScrollOffset, listScrollOffset + visibleProcessCount);
|
|
@@ -133,27 +113,6 @@ function AppContent({ store }) {
|
|
|
133
113
|
listScrollOffset,
|
|
134
114
|
visibleProcessCount
|
|
135
115
|
]);
|
|
136
|
-
// Error list modal
|
|
137
|
-
if (mode === 'errorList') {
|
|
138
|
-
return /*#__PURE__*/ _jsx(ErrorListModal, {
|
|
139
|
-
errors: failedProcesses,
|
|
140
|
-
selectedIndex: selectedErrorIndex,
|
|
141
|
-
totalErrorLines: errorLineCount
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
// Error detail modal
|
|
145
|
-
if (mode === 'errorDetail') {
|
|
146
|
-
const selectedError = store.getSelectedError();
|
|
147
|
-
if (selectedError) {
|
|
148
|
-
return /*#__PURE__*/ _jsx(ErrorDetailModal, {
|
|
149
|
-
error: selectedError,
|
|
150
|
-
currentIndex: selectedErrorIndex,
|
|
151
|
-
totalErrors: failedProcesses.length
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
// Fallback if no error selected
|
|
155
|
-
store.setMode('errorList');
|
|
156
|
-
}
|
|
157
116
|
// Normal/Interactive view - render in original registration order
|
|
158
117
|
const showSelection = mode === 'interactive';
|
|
159
118
|
return /*#__PURE__*/ _jsxs(Box, {
|
|
@@ -196,6 +155,10 @@ function AppContent({ store }) {
|
|
|
196
155
|
errorLines: errorLineCount
|
|
197
156
|
})
|
|
198
157
|
]
|
|
158
|
+
}),
|
|
159
|
+
!isInteractive && errorCount > 0 && /*#__PURE__*/ _jsx(ErrorFooter, {
|
|
160
|
+
errors: errorLines,
|
|
161
|
+
isExpanded: errorFooterExpanded
|
|
199
162
|
})
|
|
200
163
|
]
|
|
201
164
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/components/App.tsx"],"sourcesContent":["import { Box, Text, useApp, useInput, useStdin, useStdout } from 'ink';\nimport { useEffect, useMemo, useSyncExternalStore } from 'react';\nimport { EXPANDED_MAX_VISIBLE_LINES } from '../constants.ts';\nimport type { ProcessStore } from '../state/processStore.ts';\nimport { StoreContext } from '../state/StoreContext.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\ninterface AppProps {\n store: ProcessStore;\n}\n\nfunction AppContent({ store }: AppProps): React.JSX.Element {\n const { exit } = useApp();\n const { isRawModeSupported } = useStdin();\n const { stdout } = useStdout();\n const terminalHeight = stdout?.rows || 24;\n\n // Subscribe to store state\n const processes = useSyncExternalStore(store.subscribe, store.getSnapshot);\n const shouldExit = useSyncExternalStore(store.subscribe, store.getShouldExit);\n const mode = useSyncExternalStore(store.subscribe, store.getMode);\n const selectedIndex = useSyncExternalStore(store.subscribe, store.getSelectedIndex);\n const selectedErrorIndex = useSyncExternalStore(store.subscribe, store.getSelectedErrorIndex);\n const expandedId = useSyncExternalStore(store.subscribe, store.getExpandedId);\n const scrollOffset = useSyncExternalStore(store.subscribe, store.getScrollOffset);\n const listScrollOffset = useSyncExternalStore(store.subscribe, store.getListScrollOffset);\n\n // Subscribed state that triggers re-renders\n const header = useSyncExternalStore(store.subscribe, store.getHeader);\n const showStatusBar = useSyncExternalStore(store.subscribe, store.getShowStatusBar);\n const isInteractive = useSyncExternalStore(store.subscribe, store.getIsInteractive);\n\n // Calculate visible process count (reserve lines for header, divider, status bar)\n const reservedLines = (header ? 2 : 0) + (showStatusBar ? 2 : 0);\n const visibleProcessCount = Math.max(1, terminalHeight - reservedLines);\n\n // Derived state (computed from processes which is already subscribed)\n const failedProcesses = store.getFailedProcesses();\n const runningCount = store.getRunningCount();\n const doneCount = store.getDoneCount();\n const errorCount = store.getErrorCount();\n const errorLineCount = store.getErrorLineCount();\n const isAllComplete = store.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 store.setMode('interactive');\n }\n }, [isAllComplete, isInteractive, mode, store]);\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 store.setMode('errorList');\n }\n } else if (mode === 'interactive') {\n if (input === 'q' || key.escape) {\n if (expandedId) {\n store.collapse();\n } else {\n store.signalExit(() => {});\n }\n } else if (key.return) {\n store.toggleExpand();\n } else if (key.downArrow) {\n if (expandedId) {\n store.scrollDown(EXPANDED_MAX_VISIBLE_LINES);\n } else {\n store.selectNext(visibleProcessCount);\n }\n } else if (key.upArrow) {\n if (expandedId) {\n store.scrollUp();\n } else {\n store.selectPrev(visibleProcessCount);\n }\n } else if (input === 'j') {\n if (expandedId) {\n store.scrollDown(EXPANDED_MAX_VISIBLE_LINES);\n } else {\n store.selectNext(visibleProcessCount);\n }\n } else if (input === 'k') {\n if (expandedId) {\n store.scrollUp();\n } else {\n store.selectPrev(visibleProcessCount);\n }\n } else if (input === 'e' && errorCount > 0) {\n store.setMode('errorList');\n }\n } else if (mode === 'errorList') {\n if (key.escape) {\n store.setMode(isInteractive ? 'interactive' : 'normal');\n } else if (key.downArrow) {\n store.selectNextError();\n } else if (key.upArrow) {\n store.selectPrevError();\n } else if (key.return) {\n store.setMode('errorDetail');\n }\n } else if (mode === 'errorDetail') {\n if (key.escape) {\n store.setMode('errorList');\n } else if (key.downArrow) {\n store.selectNextError();\n } else if (key.upArrow) {\n store.selectPrevError();\n }\n }\n },\n { isActive: isRawModeSupported === true }\n );\n\n // Slice processes to visible viewport in interactive mode (must be before early returns)\n const visibleProcesses = useMemo(() => {\n if (mode === 'interactive') {\n return processes.slice(listScrollOffset, listScrollOffset + visibleProcessCount);\n }\n return processes;\n }, [processes, mode, listScrollOffset, visibleProcessCount]);\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 = store.getSelectedError();\n if (selectedError) {\n return <ErrorDetailModal error={selectedError} currentIndex={selectedErrorIndex} totalErrors={failedProcesses.length} />;\n }\n // Fallback if no error selected\n store.setMode('errorList');\n }\n\n // Normal/Interactive view - render in original registration order\n const showSelection = mode === 'interactive';\n\n return (\n <Box flexDirection=\"column\">\n {/* Header */}\n {header && (\n <>\n <Text>{header}</Text>\n <Divider />\n </>\n )}\n\n {/* Visible processes - key forces clean re-render on scroll */}\n <Box key={`processes-${listScrollOffset}`} flexDirection=\"column\">\n {visibleProcesses.map((item) => {\n const originalIndex = processes.indexOf(item);\n return (\n <Box key={item.id} flexDirection=\"column\">\n <CompactProcessLine item={item} isSelected={showSelection && originalIndex === selectedIndex} />\n {expandedId === item.id && <ExpandedOutput lines={item.lines} scrollOffset={scrollOffset} />}\n </Box>\n );\n })}\n </Box>\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\n// Wrapper component that provides store context\nexport default function App({ store }: AppProps): React.JSX.Element {\n return (\n <StoreContext.Provider value={store}>\n <AppContent store={store} />\n </StoreContext.Provider>\n );\n}\n"],"names":["Box","Text","useApp","useInput","useStdin","useStdout","useEffect","useMemo","useSyncExternalStore","EXPANDED_MAX_VISIBLE_LINES","StoreContext","CompactProcessLine","Divider","ErrorDetailModal","ErrorListModal","ExpandedOutput","StatusBar","AppContent","store","exit","isRawModeSupported","stdout","terminalHeight","rows","processes","subscribe","getSnapshot","shouldExit","getShouldExit","mode","getMode","selectedIndex","getSelectedIndex","selectedErrorIndex","getSelectedErrorIndex","expandedId","getExpandedId","scrollOffset","getScrollOffset","listScrollOffset","getListScrollOffset","header","getHeader","showStatusBar","getShowStatusBar","isInteractive","getIsInteractive","reservedLines","visibleProcessCount","Math","max","failedProcesses","getFailedProcesses","runningCount","getRunningCount","doneCount","getDoneCount","errorCount","getErrorCount","errorLineCount","getErrorLineCount","isAllComplete","setMode","input","key","escape","collapse","signalExit","return","toggleExpand","downArrow","scrollDown","selectNext","upArrow","scrollUp","selectPrev","selectNextError","selectPrevError","isActive","visibleProcesses","slice","errors","totalErrorLines","selectedError","getSelectedError","error","currentIndex","totalErrors","length","showSelection","flexDirection","map","item","originalIndex","indexOf","isSelected","id","lines","running","done","errorLines","App","Provider","value"],"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, useStdout } from 'ink';\nimport { useEffect, useMemo, useSyncExternalStore } from 'react';\nimport { EXPANDED_MAX_VISIBLE_LINES } from '../constants.ts';\nimport type { ProcessStore } from '../state/processStore.ts';\nimport { StoreContext } from '../state/StoreContext.ts';\nimport CompactProcessLine from './CompactProcessLine.ts';\nimport Divider from './Divider.ts';\nimport ErrorFooter from './ErrorFooter.ts';\nimport ExpandedOutput from './ExpandedOutput.ts';\nimport StatusBar from './StatusBar.ts';\n\ninterface AppProps {\n store: ProcessStore;\n}\n\nfunction AppContent({ store }: AppProps): React.JSX.Element {\n const { exit } = useApp();\n const { isRawModeSupported } = useStdin();\n const { stdout } = useStdout();\n const terminalHeight = stdout?.rows || 24;\n\n // Subscribe to store state\n const processes = useSyncExternalStore(store.subscribe, store.getSnapshot);\n const shouldExit = useSyncExternalStore(store.subscribe, store.getShouldExit);\n const mode = useSyncExternalStore(store.subscribe, store.getMode);\n const selectedIndex = useSyncExternalStore(store.subscribe, store.getSelectedIndex);\n const expandedId = useSyncExternalStore(store.subscribe, store.getExpandedId);\n const scrollOffset = useSyncExternalStore(store.subscribe, store.getScrollOffset);\n const listScrollOffset = useSyncExternalStore(store.subscribe, store.getListScrollOffset);\n const errorFooterExpanded = useSyncExternalStore(store.subscribe, store.getErrorFooterExpanded);\n\n // Subscribed state that triggers re-renders\n const header = useSyncExternalStore(store.subscribe, store.getHeader);\n const showStatusBar = useSyncExternalStore(store.subscribe, store.getShowStatusBar);\n const isInteractive = useSyncExternalStore(store.subscribe, store.getIsInteractive);\n\n // Calculate visible process count (reserve lines for header, divider, status bar)\n const reservedLines = (header ? 2 : 0) + (showStatusBar ? 2 : 0);\n const visibleProcessCount = Math.max(1, terminalHeight - reservedLines);\n\n // Derived state (computed from processes which is already subscribed)\n const runningCount = store.getRunningCount();\n const doneCount = store.getDoneCount();\n const errorCount = store.getErrorCount();\n const errorLineCount = store.getErrorLineCount();\n const isAllComplete = store.isAllComplete();\n const errorLines = store.getErrorLines();\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 store.setMode('interactive');\n }\n }, [isAllComplete, isInteractive, mode, store]);\n\n // Keyboard handling (only active when raw mode is supported)\n useInput(\n (input, key) => {\n if (mode === 'normal') {\n // In non-interactive mode, 'e' toggles error footer\n if (input === 'e' && errorCount > 0) {\n store.toggleErrorFooter();\n }\n } else if (mode === 'interactive') {\n if (input === 'q' || key.escape) {\n if (expandedId) {\n store.collapse();\n } else {\n store.signalExit(() => {});\n }\n } else if (key.return) {\n store.toggleExpand();\n } else if (key.downArrow) {\n if (expandedId) {\n store.scrollDown(EXPANDED_MAX_VISIBLE_LINES);\n } else {\n store.selectNext(visibleProcessCount);\n }\n } else if (key.upArrow) {\n if (expandedId) {\n store.scrollUp();\n } else {\n store.selectPrev(visibleProcessCount);\n }\n } else if (input === 'j') {\n if (expandedId) {\n store.scrollDown(EXPANDED_MAX_VISIBLE_LINES);\n } else {\n store.selectNext(visibleProcessCount);\n }\n } else if (input === 'k') {\n if (expandedId) {\n store.scrollUp();\n } else {\n store.selectPrev(visibleProcessCount);\n }\n }\n }\n },\n { isActive: isRawModeSupported === true }\n );\n\n // Slice processes to visible viewport in interactive mode\n const visibleProcesses = useMemo(() => {\n if (mode === 'interactive') {\n return processes.slice(listScrollOffset, listScrollOffset + visibleProcessCount);\n }\n return processes;\n }, [processes, mode, listScrollOffset, visibleProcessCount]);\n\n // Normal/Interactive view - render in original registration order\n const showSelection = mode === 'interactive';\n\n return (\n <Box flexDirection=\"column\">\n {/* Header */}\n {header && (\n <>\n <Text>{header}</Text>\n <Divider />\n </>\n )}\n\n {/* Visible processes - key forces clean re-render on scroll */}\n <Box key={`processes-${listScrollOffset}`} flexDirection=\"column\">\n {visibleProcesses.map((item) => {\n const originalIndex = processes.indexOf(item);\n return (\n <Box key={item.id} flexDirection=\"column\">\n <CompactProcessLine item={item} isSelected={showSelection && originalIndex === selectedIndex} />\n {expandedId === item.id && <ExpandedOutput lines={item.lines} scrollOffset={scrollOffset} />}\n </Box>\n );\n })}\n </Box>\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\n {/* Error footer (non-interactive mode only) */}\n {!isInteractive && errorCount > 0 && <ErrorFooter errors={errorLines} isExpanded={errorFooterExpanded} />}\n </Box>\n );\n}\n\n// Wrapper component that provides store context\nexport default function App({ store }: AppProps): React.JSX.Element {\n return (\n <StoreContext.Provider value={store}>\n <AppContent store={store} />\n </StoreContext.Provider>\n );\n}\n"],"names":["Box","Text","useApp","useInput","useStdin","useStdout","useEffect","useMemo","useSyncExternalStore","EXPANDED_MAX_VISIBLE_LINES","StoreContext","CompactProcessLine","Divider","ErrorFooter","ExpandedOutput","StatusBar","AppContent","store","exit","isRawModeSupported","stdout","terminalHeight","rows","processes","subscribe","getSnapshot","shouldExit","getShouldExit","mode","getMode","selectedIndex","getSelectedIndex","expandedId","getExpandedId","scrollOffset","getScrollOffset","listScrollOffset","getListScrollOffset","errorFooterExpanded","getErrorFooterExpanded","header","getHeader","showStatusBar","getShowStatusBar","isInteractive","getIsInteractive","reservedLines","visibleProcessCount","Math","max","runningCount","getRunningCount","doneCount","getDoneCount","errorCount","getErrorCount","errorLineCount","getErrorLineCount","isAllComplete","errorLines","getErrorLines","setMode","input","key","toggleErrorFooter","escape","collapse","signalExit","return","toggleExpand","downArrow","scrollDown","selectNext","upArrow","scrollUp","selectPrev","isActive","visibleProcesses","slice","showSelection","flexDirection","map","item","originalIndex","indexOf","isSelected","id","lines","length","running","done","errors","isExpanded","App","Provider","value"],"mappings":"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"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { memo } from 'react';
|
|
4
|
+
import { LineType } from '../types.js';
|
|
5
|
+
import Divider from './Divider.js';
|
|
6
|
+
export default /*#__PURE__*/ memo(function ErrorFooter({ errors, isExpanded }) {
|
|
7
|
+
// Calculate totals for collapsed summary
|
|
8
|
+
const totalLines = errors.reduce((sum, e)=>sum + e.lines.filter((l)=>l.type === LineType.stderr).length, 0);
|
|
9
|
+
const totalProcesses = errors.length;
|
|
10
|
+
if (totalProcesses === 0) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
const processText = totalProcesses === 1 ? 'process' : 'processes';
|
|
14
|
+
if (!isExpanded) {
|
|
15
|
+
// Collapsed view - single summary line
|
|
16
|
+
const summary = totalLines > 0 ? `${totalLines} error line${totalLines === 1 ? '' : 's'} in ${totalProcesses} ${processText}` : `${totalProcesses} failed ${processText}`;
|
|
17
|
+
return /*#__PURE__*/ _jsxs(_Fragment, {
|
|
18
|
+
children: [
|
|
19
|
+
/*#__PURE__*/ _jsx(Divider, {}),
|
|
20
|
+
/*#__PURE__*/ _jsxs(Text, {
|
|
21
|
+
children: [
|
|
22
|
+
/*#__PURE__*/ _jsx(Text, {
|
|
23
|
+
color: "red",
|
|
24
|
+
children: '\u25b8'
|
|
25
|
+
}),
|
|
26
|
+
` ${summary} `,
|
|
27
|
+
/*#__PURE__*/ _jsx(Text, {
|
|
28
|
+
dimColor: true,
|
|
29
|
+
children: "[e]"
|
|
30
|
+
})
|
|
31
|
+
]
|
|
32
|
+
})
|
|
33
|
+
]
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
// Expanded view - show all error lines (or just process names if no stderr)
|
|
37
|
+
return /*#__PURE__*/ _jsxs(_Fragment, {
|
|
38
|
+
children: [
|
|
39
|
+
/*#__PURE__*/ _jsx(Divider, {}),
|
|
40
|
+
/*#__PURE__*/ _jsxs(Text, {
|
|
41
|
+
children: [
|
|
42
|
+
/*#__PURE__*/ _jsx(Text, {
|
|
43
|
+
color: "red",
|
|
44
|
+
children: '\u25be'
|
|
45
|
+
}),
|
|
46
|
+
' Errors ',
|
|
47
|
+
/*#__PURE__*/ _jsx(Text, {
|
|
48
|
+
dimColor: true,
|
|
49
|
+
children: "[e]"
|
|
50
|
+
})
|
|
51
|
+
]
|
|
52
|
+
}),
|
|
53
|
+
/*#__PURE__*/ _jsx(Box, {
|
|
54
|
+
flexDirection: "column",
|
|
55
|
+
children: errors.map((errorGroup)=>{
|
|
56
|
+
const stderrLines = errorGroup.lines.filter((line)=>line.type === LineType.stderr);
|
|
57
|
+
if (stderrLines.length === 0) {
|
|
58
|
+
// No stderr output - just show process name
|
|
59
|
+
return /*#__PURE__*/ _jsxs(Text, {
|
|
60
|
+
children: [
|
|
61
|
+
/*#__PURE__*/ _jsxs(Text, {
|
|
62
|
+
dimColor: true,
|
|
63
|
+
children: [
|
|
64
|
+
"[",
|
|
65
|
+
errorGroup.processName,
|
|
66
|
+
"]"
|
|
67
|
+
]
|
|
68
|
+
}),
|
|
69
|
+
" ",
|
|
70
|
+
/*#__PURE__*/ _jsx(Text, {
|
|
71
|
+
color: "red",
|
|
72
|
+
children: "(failed)"
|
|
73
|
+
})
|
|
74
|
+
]
|
|
75
|
+
}, errorGroup.processName);
|
|
76
|
+
}
|
|
77
|
+
return stderrLines.map((line, index)=>/*#__PURE__*/ _jsxs(Text, {
|
|
78
|
+
children: [
|
|
79
|
+
/*#__PURE__*/ _jsxs(Text, {
|
|
80
|
+
dimColor: true,
|
|
81
|
+
children: [
|
|
82
|
+
"[",
|
|
83
|
+
errorGroup.processName,
|
|
84
|
+
"]"
|
|
85
|
+
]
|
|
86
|
+
}),
|
|
87
|
+
" ",
|
|
88
|
+
line.text
|
|
89
|
+
]
|
|
90
|
+
}, `${errorGroup.processName}-${index}`));
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
]
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/components/ErrorFooter.tsx"],"sourcesContent":["import { Box, Text } from 'ink';\nimport { memo } from 'react';\nimport type { Line } from '../types.ts';\nimport { LineType } from '../types.ts';\nimport Divider from './Divider.ts';\n\ntype ErrorGroup = {\n processName: string;\n lines: Line[];\n};\n\ntype Props = {\n errors: ErrorGroup[];\n isExpanded: boolean;\n};\n\nexport default memo(function ErrorFooter({ errors, isExpanded }: Props) {\n // Calculate totals for collapsed summary\n const totalLines = errors.reduce((sum, e) => sum + e.lines.filter((l) => l.type === LineType.stderr).length, 0);\n const totalProcesses = errors.length;\n\n if (totalProcesses === 0) {\n return null;\n }\n\n const processText = totalProcesses === 1 ? 'process' : 'processes';\n\n if (!isExpanded) {\n // Collapsed view - single summary line\n const summary = totalLines > 0 ? `${totalLines} error line${totalLines === 1 ? '' : 's'} in ${totalProcesses} ${processText}` : `${totalProcesses} failed ${processText}`;\n return (\n <>\n <Divider />\n <Text>\n <Text color=\"red\">{'\\u25b8'}</Text>\n {` ${summary} `}\n <Text dimColor>[e]</Text>\n </Text>\n </>\n );\n }\n\n // Expanded view - show all error lines (or just process names if no stderr)\n return (\n <>\n <Divider />\n <Text>\n <Text color=\"red\">{'\\u25be'}</Text>\n {' Errors '}\n <Text dimColor>[e]</Text>\n </Text>\n <Box flexDirection=\"column\">\n {errors.map((errorGroup) => {\n const stderrLines = errorGroup.lines.filter((line) => line.type === LineType.stderr);\n if (stderrLines.length === 0) {\n // No stderr output - just show process name\n return (\n <Text key={errorGroup.processName}>\n <Text dimColor>[{errorGroup.processName}]</Text> <Text color=\"red\">(failed)</Text>\n </Text>\n );\n }\n return stderrLines.map((line, index) => (\n <Text key={`${errorGroup.processName}-${index}`}>\n <Text dimColor>[{errorGroup.processName}]</Text> {line.text}\n </Text>\n ));\n })}\n </Box>\n </>\n );\n});\n"],"names":["Box","Text","memo","LineType","Divider","ErrorFooter","errors","isExpanded","totalLines","reduce","sum","e","lines","filter","l","type","stderr","length","totalProcesses","processText","summary","color","dimColor","flexDirection","map","errorGroup","stderrLines","line","processName","index","text"],"mappings":";AAAA,SAASA,GAAG,EAAEC,IAAI,QAAQ,MAAM;AAChC,SAASC,IAAI,QAAQ,QAAQ;AAE7B,SAASC,QAAQ,QAAQ,cAAc;AACvC,OAAOC,aAAa,eAAe;AAYnC,6BAAeF,KAAK,SAASG,YAAY,EAAEC,MAAM,EAAEC,UAAU,EAAS;IACpE,yCAAyC;IACzC,MAAMC,aAAaF,OAAOG,MAAM,CAAC,CAACC,KAAKC,IAAMD,MAAMC,EAAEC,KAAK,CAACC,MAAM,CAAC,CAACC,IAAMA,EAAEC,IAAI,KAAKZ,SAASa,MAAM,EAAEC,MAAM,EAAE;IAC7G,MAAMC,iBAAiBZ,OAAOW,MAAM;IAEpC,IAAIC,mBAAmB,GAAG;QACxB,OAAO;IACT;IAEA,MAAMC,cAAcD,mBAAmB,IAAI,YAAY;IAEvD,IAAI,CAACX,YAAY;QACf,uCAAuC;QACvC,MAAMa,UAAUZ,aAAa,IAAI,GAAGA,WAAW,WAAW,EAAEA,eAAe,IAAI,KAAK,IAAI,IAAI,EAAEU,eAAe,CAAC,EAAEC,aAAa,GAAG,GAAGD,eAAe,QAAQ,EAAEC,aAAa;QACzK,qBACE;;8BACE,KAACf;8BACD,MAACH;;sCACC,KAACA;4BAAKoB,OAAM;sCAAO;;wBAClB,CAAC,CAAC,EAAED,QAAQ,CAAC,CAAC;sCACf,KAACnB;4BAAKqB,QAAQ;sCAAC;;;;;;IAIvB;IAEA,4EAA4E;IAC5E,qBACE;;0BACE,KAAClB;0BACD,MAACH;;kCACC,KAACA;wBAAKoB,OAAM;kCAAO;;oBAClB;kCACD,KAACpB;wBAAKqB,QAAQ;kCAAC;;;;0BAEjB,KAACtB;gBAAIuB,eAAc;0BAChBjB,OAAOkB,GAAG,CAAC,CAACC;oBACX,MAAMC,cAAcD,WAAWb,KAAK,CAACC,MAAM,CAAC,CAACc,OAASA,KAAKZ,IAAI,KAAKZ,SAASa,MAAM;oBACnF,IAAIU,YAAYT,MAAM,KAAK,GAAG;wBAC5B,4CAA4C;wBAC5C,qBACE,MAAChB;;8CACC,MAACA;oCAAKqB,QAAQ;;wCAAC;wCAAEG,WAAWG,WAAW;wCAAC;;;gCAAQ;8CAAC,KAAC3B;oCAAKoB,OAAM;8CAAM;;;2BAD1DI,WAAWG,WAAW;oBAIrC;oBACA,OAAOF,YAAYF,GAAG,CAAC,CAACG,MAAME,sBAC5B,MAAC5B;;8CACC,MAACA;oCAAKqB,QAAQ;;wCAAC;wCAAEG,WAAWG,WAAW;wCAAC;;;gCAAQ;gCAAED,KAAKG,IAAI;;2BADlD,GAAGL,WAAWG,WAAW,CAAC,CAAC,EAAEC,OAAO;gBAInD;;;;AAIR,GAAG"}
|
|
@@ -2,7 +2,6 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
3
|
import { memo } from 'react';
|
|
4
4
|
import { EXPANDED_MAX_VISIBLE_LINES } from '../constants.js';
|
|
5
|
-
import { LineType } from '../types.js';
|
|
6
5
|
export default /*#__PURE__*/ memo(function ExpandedOutput({ lines, scrollOffset, maxVisible = EXPANDED_MAX_VISIBLE_LINES }) {
|
|
7
6
|
const visibleLines = lines.slice(scrollOffset, scrollOffset + maxVisible);
|
|
8
7
|
const hasMore = lines.length > scrollOffset + maxVisible;
|
|
@@ -22,7 +21,6 @@ export default /*#__PURE__*/ memo(function ExpandedOutput({ lines, scrollOffset,
|
|
|
22
21
|
children: [
|
|
23
22
|
visibleLines.map((line, i)=>// biome-ignore lint/suspicious/noArrayIndexKey: Lines have no unique ID, index is stable for this scrolling view
|
|
24
23
|
/*#__PURE__*/ _jsxs(Text, {
|
|
25
|
-
color: line.type === LineType.stderr ? 'red' : undefined,
|
|
26
24
|
children: [
|
|
27
25
|
"│ ",
|
|
28
26
|
line.text
|
|
@@ -1 +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';\
|
|
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';\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}>│ {line.text}</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","ExpandedOutput","lines","scrollOffset","maxVisible","visibleLines","slice","hasMore","length","remaining","paddingLeft","dimColor","flexDirection","map","line","i","text"],"mappings":";AAAA,SAASA,GAAG,EAAEC,IAAI,QAAQ,MAAM;AAChC,SAASC,IAAI,QAAQ,QAAQ;AAC7B,SAASC,0BAA0B,QAAQ,kBAAkB;AAS7D,6BAAeD,KAAK,SAASE,eAAe,EAAEC,KAAK,EAAEC,YAAY,EAAEC,aAAaJ,0BAA0B,EAAS;IACjH,MAAMK,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,KAACX;YAAIa,aAAa;sBAChB,cAAA,KAACZ;gBAAKa,QAAQ;0BAAC;;;IAGrB;IAEA,qBACE,MAACd;QAAIe,eAAc;QAASF,aAAa;;YACtCL,aAAaQ,GAAG,CAAC,CAACC,MAAMC,IACvB,iHAAiH;8BACjH,MAACjB;;wBAA4B;wBAAGgB,KAAKE,IAAI;;mBAA9Bb,eAAeY;YAE3BR,yBAAW,MAACT;gBAAKa,QAAQ;;oBAAC;oBAAKF;oBAAU;;;;;AAGhD,GAAG"}
|
|
@@ -33,40 +33,29 @@ import { SPINNER } from '../constants.js';
|
|
|
33
33
|
import figures from '../lib/figures.js';
|
|
34
34
|
import Spinner from './Spinner.js';
|
|
35
35
|
export default /*#__PURE__*/ memo(function StatusBar({ running, done, errors, errorLines }) {
|
|
36
|
-
return /*#__PURE__*/
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
children:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}),
|
|
56
|
-
` Errors: ${errors}`,
|
|
57
|
-
errorLines > 0 && /*#__PURE__*/ _jsx(Text, {
|
|
58
|
-
dimColor: true,
|
|
59
|
-
children: ` (${errorLines} lines)`
|
|
60
|
-
})
|
|
61
|
-
]
|
|
62
|
-
})
|
|
63
|
-
}),
|
|
64
|
-
errors > 0 && /*#__PURE__*/ _jsx(Box, {
|
|
65
|
-
children: /*#__PURE__*/ _jsx(Text, {
|
|
36
|
+
return /*#__PURE__*/ _jsx(Box, {
|
|
37
|
+
children: /*#__PURE__*/ _jsxs(Text, {
|
|
38
|
+
children: [
|
|
39
|
+
running > 0 ? /*#__PURE__*/ _jsx(Spinner, _object_spread({}, SPINNER)) : /*#__PURE__*/ _jsx(Text, {
|
|
40
|
+
color: "green",
|
|
41
|
+
children: figures.tick
|
|
42
|
+
}),
|
|
43
|
+
` Running: ${running} | `,
|
|
44
|
+
/*#__PURE__*/ _jsx(Text, {
|
|
45
|
+
color: "green",
|
|
46
|
+
children: figures.tick
|
|
47
|
+
}),
|
|
48
|
+
` Done: ${done} | `,
|
|
49
|
+
/*#__PURE__*/ _jsx(Text, {
|
|
50
|
+
color: "red",
|
|
51
|
+
children: figures.cross
|
|
52
|
+
}),
|
|
53
|
+
` Errors: ${errors}`,
|
|
54
|
+
errorLines > 0 && /*#__PURE__*/ _jsx(Text, {
|
|
66
55
|
dimColor: true,
|
|
67
|
-
children:
|
|
56
|
+
children: ` (${errorLines} lines)`
|
|
68
57
|
})
|
|
69
|
-
|
|
70
|
-
|
|
58
|
+
]
|
|
59
|
+
})
|
|
71
60
|
});
|
|
72
61
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/components/StatusBar.tsx"],"sourcesContent":["import { Box, Text } from 'ink';\nimport { memo } from 'react';\nimport { SPINNER } from '../constants.ts';\nimport figures from '../lib/figures.ts';\nimport Spinner from './Spinner.ts';\n\ntype Props = {\n running: number;\n done: number;\n errors: number;\n errorLines: number;\n};\n\nexport default memo(function StatusBar({ running, done, errors, errorLines }: Props) {\n return (\n <Box
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/components/StatusBar.tsx"],"sourcesContent":["import { Box, Text } from 'ink';\nimport { memo } from 'react';\nimport { SPINNER } from '../constants.ts';\nimport figures from '../lib/figures.ts';\nimport Spinner from './Spinner.ts';\n\ntype Props = {\n running: number;\n done: number;\n errors: number;\n errorLines: number;\n};\n\nexport default memo(function StatusBar({ running, done, errors, errorLines }: Props) {\n return (\n <Box>\n <Text>\n {running > 0 ? <Spinner {...SPINNER} /> : <Text color=\"green\">{figures.tick}</Text>}\n {` Running: ${running} | `}\n <Text color=\"green\">{figures.tick}</Text>\n {` Done: ${done} | `}\n <Text color=\"red\">{figures.cross}</Text>\n {` Errors: ${errors}`}\n {errorLines > 0 && <Text dimColor>{` (${errorLines} lines)`}</Text>}\n </Text>\n </Box>\n );\n});\n"],"names":["Box","Text","memo","SPINNER","figures","Spinner","StatusBar","running","done","errors","errorLines","color","tick","cross","dimColor"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAASA,GAAG,EAAEC,IAAI,QAAQ,MAAM;AAChC,SAASC,IAAI,QAAQ,QAAQ;AAC7B,SAASC,OAAO,QAAQ,kBAAkB;AAC1C,OAAOC,aAAa,oBAAoB;AACxC,OAAOC,aAAa,eAAe;AASnC,6BAAeH,KAAK,SAASI,UAAU,EAAEC,OAAO,EAAEC,IAAI,EAAEC,MAAM,EAAEC,UAAU,EAAS;IACjF,qBACE,KAACV;kBACC,cAAA,MAACC;;gBACEM,UAAU,kBAAI,KAACF,4BAAYF,0BAAc,KAACF;oBAAKU,OAAM;8BAASP,QAAQQ,IAAI;;gBAC1E,CAAC,UAAU,EAAEL,QAAQ,IAAI,CAAC;8BAC3B,KAACN;oBAAKU,OAAM;8BAASP,QAAQQ,IAAI;;gBAChC,CAAC,OAAO,EAAEJ,KAAK,IAAI,CAAC;8BACrB,KAACP;oBAAKU,OAAM;8BAAOP,QAAQS,KAAK;;gBAC/B,CAAC,SAAS,EAAEJ,QAAQ;gBACpBC,aAAa,mBAAK,KAACT;oBAAKa,QAAQ;8BAAE,CAAC,EAAE,EAAEJ,WAAW,OAAO,CAAC;;;;;AAInE,GAAG"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Line } from '../types.js';
|
|
2
|
+
type ErrorGroup = {
|
|
3
|
+
processName: string;
|
|
4
|
+
lines: Line[];
|
|
5
|
+
};
|
|
6
|
+
type Props = {
|
|
7
|
+
errors: ErrorGroup[];
|
|
8
|
+
isExpanded: boolean;
|
|
9
|
+
};
|
|
10
|
+
declare const _default: import("react").NamedExoticComponent<Props>;
|
|
11
|
+
export default _default;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ChildProcess, Line, SessionOptions } from '../types.js';
|
|
2
2
|
type Listener = () => void;
|
|
3
|
-
type Mode = 'normal' | 'interactive'
|
|
3
|
+
type Mode = 'normal' | 'interactive';
|
|
4
4
|
export declare class ProcessStore {
|
|
5
5
|
private processes;
|
|
6
6
|
private completedIds;
|
|
@@ -9,10 +9,10 @@ export declare class ProcessStore {
|
|
|
9
9
|
private exitCallback;
|
|
10
10
|
private mode;
|
|
11
11
|
private selectedIndex;
|
|
12
|
-
private selectedErrorIndex;
|
|
13
12
|
private expandedId;
|
|
14
13
|
private scrollOffset;
|
|
15
14
|
private listScrollOffset;
|
|
15
|
+
private errorFooterExpanded;
|
|
16
16
|
private header;
|
|
17
17
|
private showStatusBar;
|
|
18
18
|
private isInteractive;
|
|
@@ -29,10 +29,10 @@ export declare class ProcessStore {
|
|
|
29
29
|
getErrorLineCount: () => number;
|
|
30
30
|
getMode: () => Mode;
|
|
31
31
|
getSelectedIndex: () => number;
|
|
32
|
-
getSelectedErrorIndex: () => number;
|
|
33
32
|
getExpandedId: () => string | null;
|
|
34
33
|
getScrollOffset: () => number;
|
|
35
34
|
getListScrollOffset: () => number;
|
|
35
|
+
getErrorFooterExpanded: () => boolean;
|
|
36
36
|
getHeader: () => string | undefined;
|
|
37
37
|
getShowStatusBar: () => boolean;
|
|
38
38
|
getIsInteractive: () => boolean;
|
|
@@ -46,9 +46,12 @@ export declare class ProcessStore {
|
|
|
46
46
|
selectPrev(visibleCount?: number): void;
|
|
47
47
|
private adjustListScroll;
|
|
48
48
|
getSelectedProcess(): ChildProcess | undefined;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
toggleErrorFooter(): void;
|
|
50
|
+
expandErrorFooter(): void;
|
|
51
|
+
getErrorLines(): Array<{
|
|
52
|
+
processName: string;
|
|
53
|
+
lines: Line[];
|
|
54
|
+
}>;
|
|
52
55
|
toggleExpand(): void;
|
|
53
56
|
collapse(): void;
|
|
54
57
|
scrollDown(maxVisible: number): void;
|