spawn-term 2.1.1 → 3.0.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 (63) hide show
  1. package/dist/cjs/components/App.js +62 -46
  2. package/dist/cjs/components/App.js.map +1 -1
  3. package/dist/cjs/components/CompactProcessLine.js +3 -2
  4. package/dist/cjs/components/CompactProcessLine.js.map +1 -1
  5. package/dist/cjs/index-cjs.js +3 -3
  6. package/dist/cjs/index-cjs.js.map +1 -1
  7. package/dist/cjs/index-esm.js +4 -4
  8. package/dist/cjs/index-esm.js.map +1 -1
  9. package/dist/cjs/session.js +311 -0
  10. package/dist/cjs/session.js.map +1 -0
  11. package/dist/cjs/src/components/App.d.ts +6 -1
  12. package/dist/cjs/src/index-cjs.d.ts +3 -3
  13. package/dist/cjs/src/index-esm.d.ts +3 -3
  14. package/dist/cjs/src/session.d.ts +7 -0
  15. package/dist/cjs/src/state/StoreContext.d.ts +3 -0
  16. package/dist/cjs/src/state/processStore.d.ts +6 -4
  17. package/dist/cjs/src/types.d.ts +5 -6
  18. package/dist/cjs/state/StoreContext.js +28 -0
  19. package/dist/cjs/state/StoreContext.js.map +1 -0
  20. package/dist/cjs/state/processStore.js +15 -17
  21. package/dist/cjs/state/processStore.js.map +1 -1
  22. package/dist/cjs/types.js.map +1 -1
  23. package/dist/esm/components/App.js +59 -45
  24. package/dist/esm/components/App.js.map +1 -1
  25. package/dist/esm/components/CompactProcessLine.js +3 -2
  26. package/dist/esm/components/CompactProcessLine.js.map +1 -1
  27. package/dist/esm/index-cjs.js +1 -1
  28. package/dist/esm/index-cjs.js.map +1 -1
  29. package/dist/esm/index-esm.js +2 -2
  30. package/dist/esm/index-esm.js.map +1 -1
  31. package/dist/esm/session.js +218 -0
  32. package/dist/esm/session.js.map +1 -0
  33. package/dist/esm/src/components/App.d.ts +6 -1
  34. package/dist/esm/src/index-cjs.d.ts +3 -3
  35. package/dist/esm/src/index-esm.d.ts +3 -3
  36. package/dist/esm/src/session.d.ts +7 -0
  37. package/dist/esm/src/state/StoreContext.d.ts +3 -0
  38. package/dist/esm/src/state/processStore.d.ts +6 -4
  39. package/dist/esm/src/types.d.ts +5 -6
  40. package/dist/esm/state/StoreContext.js +9 -0
  41. package/dist/esm/state/StoreContext.js.map +1 -0
  42. package/dist/esm/state/processStore.js +13 -13
  43. package/dist/esm/state/processStore.js.map +1 -1
  44. package/dist/esm/types.js.map +1 -1
  45. package/package.json +1 -1
  46. package/dist/cjs/createApp.js +0 -58
  47. package/dist/cjs/createApp.js.map +0 -1
  48. package/dist/cjs/spawnTerminal.js +0 -75
  49. package/dist/cjs/spawnTerminal.js.map +0 -1
  50. package/dist/cjs/src/createApp.d.ts +0 -6
  51. package/dist/cjs/src/spawnTerminal.d.ts +0 -2
  52. package/dist/cjs/src/worker.d.ts +0 -2
  53. package/dist/cjs/worker.js +0 -236
  54. package/dist/cjs/worker.js.map +0 -1
  55. package/dist/esm/createApp.js +0 -42
  56. package/dist/esm/createApp.js.map +0 -1
  57. package/dist/esm/spawnTerminal.js +0 -19
  58. package/dist/esm/spawnTerminal.js.map +0 -1
  59. package/dist/esm/src/createApp.d.ts +0 -6
  60. package/dist/esm/src/spawnTerminal.d.ts +0 -2
  61. package/dist/esm/src/worker.d.ts +0 -2
  62. package/dist/esm/worker.js +0 -175
  63. package/dist/esm/worker.js.map +0 -1
@@ -2,35 +2,35 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import { Box, Text, useApp, useInput, useStdin } from 'ink';
3
3
  import { useEffect, useSyncExternalStore } from 'react';
4
4
  import { EXPANDED_MAX_VISIBLE_LINES } from '../constants.js';
5
- import { processStore } from '../state/processStore.js';
5
+ import { StoreContext } from '../state/StoreContext.js';
6
6
  import CompactProcessLine from './CompactProcessLine.js';
7
7
  import Divider from './Divider.js';
8
8
  import ErrorDetailModal from './ErrorDetailModal.js';
9
9
  import ErrorListModal from './ErrorListModal.js';
10
10
  import ExpandedOutput from './ExpandedOutput.js';
11
11
  import StatusBar from './StatusBar.js';
12
- export default function App() {
12
+ function AppContent({ store }) {
13
13
  const { exit } = useApp();
14
14
  const { isRawModeSupported } = useStdin();
15
15
  // Subscribe to store state
16
- const processes = useSyncExternalStore(processStore.subscribe, processStore.getSnapshot);
17
- const shouldExit = useSyncExternalStore(processStore.subscribe, processStore.getShouldExit);
18
- const mode = useSyncExternalStore(processStore.subscribe, processStore.getMode);
19
- const selectedIndex = useSyncExternalStore(processStore.subscribe, processStore.getSelectedIndex);
20
- const selectedErrorIndex = useSyncExternalStore(processStore.subscribe, processStore.getSelectedErrorIndex);
21
- const expandedId = useSyncExternalStore(processStore.subscribe, processStore.getExpandedId);
22
- const scrollOffset = useSyncExternalStore(processStore.subscribe, processStore.getScrollOffset);
16
+ const processes = useSyncExternalStore(store.subscribe, store.getSnapshot);
17
+ const shouldExit = useSyncExternalStore(store.subscribe, store.getShouldExit);
18
+ const mode = useSyncExternalStore(store.subscribe, store.getMode);
19
+ const selectedIndex = useSyncExternalStore(store.subscribe, store.getSelectedIndex);
20
+ const selectedErrorIndex = useSyncExternalStore(store.subscribe, store.getSelectedErrorIndex);
21
+ const expandedId = useSyncExternalStore(store.subscribe, store.getExpandedId);
22
+ const scrollOffset = useSyncExternalStore(store.subscribe, store.getScrollOffset);
23
23
  // Subscribed state that triggers re-renders
24
- const header = useSyncExternalStore(processStore.subscribe, processStore.getHeader);
25
- const showStatusBar = useSyncExternalStore(processStore.subscribe, processStore.getShowStatusBar);
26
- const isInteractive = useSyncExternalStore(processStore.subscribe, processStore.getIsInteractive);
24
+ const header = useSyncExternalStore(store.subscribe, store.getHeader);
25
+ const showStatusBar = useSyncExternalStore(store.subscribe, store.getShowStatusBar);
26
+ const isInteractive = useSyncExternalStore(store.subscribe, store.getIsInteractive);
27
27
  // Derived state (computed from processes which is already subscribed)
28
- const failedProcesses = processStore.getFailedProcesses();
29
- const runningCount = processStore.getRunningCount();
30
- const doneCount = processStore.getDoneCount();
31
- const errorCount = processStore.getErrorCount();
32
- const errorLineCount = processStore.getErrorLineCount();
33
- const isAllComplete = processStore.isAllComplete();
28
+ const failedProcesses = store.getFailedProcesses();
29
+ const runningCount = store.getRunningCount();
30
+ const doneCount = store.getDoneCount();
31
+ const errorCount = store.getErrorCount();
32
+ const errorLineCount = store.getErrorLineCount();
33
+ const isAllComplete = store.isAllComplete();
34
34
  // Handle exit signal
35
35
  useEffect(()=>{
36
36
  if (shouldExit) {
@@ -43,72 +43,73 @@ export default function App() {
43
43
  // Auto-enter interactive mode when all complete and interactive flag is set
44
44
  useEffect(()=>{
45
45
  if (isAllComplete && isInteractive && mode === 'normal') {
46
- processStore.setMode('interactive');
46
+ store.setMode('interactive');
47
47
  }
48
48
  }, [
49
49
  isAllComplete,
50
50
  isInteractive,
51
- mode
51
+ mode,
52
+ store
52
53
  ]);
53
54
  // Keyboard handling (only active when raw mode is supported)
54
55
  useInput((input, key)=>{
55
56
  if (mode === 'normal') {
56
57
  if (input === 'e' && errorCount > 0) {
57
- processStore.setMode('errorList');
58
+ store.setMode('errorList');
58
59
  }
59
60
  } else if (mode === 'interactive') {
60
61
  if (input === 'q' || key.escape) {
61
62
  if (expandedId) {
62
- processStore.collapse();
63
+ store.collapse();
63
64
  } else {
64
- processStore.signalExit(()=>{});
65
+ store.signalExit(()=>{});
65
66
  }
66
67
  } else if (key.return) {
67
- processStore.toggleExpand();
68
+ store.toggleExpand();
68
69
  } else if (key.downArrow) {
69
70
  if (expandedId) {
70
- processStore.scrollDown(EXPANDED_MAX_VISIBLE_LINES);
71
+ store.scrollDown(EXPANDED_MAX_VISIBLE_LINES);
71
72
  } else {
72
- processStore.selectNext();
73
+ store.selectNext();
73
74
  }
74
75
  } else if (key.upArrow) {
75
76
  if (expandedId) {
76
- processStore.scrollUp();
77
+ store.scrollUp();
77
78
  } else {
78
- processStore.selectPrev();
79
+ store.selectPrev();
79
80
  }
80
81
  } else if (input === 'j') {
81
82
  if (expandedId) {
82
- processStore.scrollDown(EXPANDED_MAX_VISIBLE_LINES);
83
+ store.scrollDown(EXPANDED_MAX_VISIBLE_LINES);
83
84
  } else {
84
- processStore.selectNext();
85
+ store.selectNext();
85
86
  }
86
87
  } else if (input === 'k') {
87
88
  if (expandedId) {
88
- processStore.scrollUp();
89
+ store.scrollUp();
89
90
  } else {
90
- processStore.selectPrev();
91
+ store.selectPrev();
91
92
  }
92
93
  } else if (input === 'e' && errorCount > 0) {
93
- processStore.setMode('errorList');
94
+ store.setMode('errorList');
94
95
  }
95
96
  } else if (mode === 'errorList') {
96
97
  if (key.escape) {
97
- processStore.setMode(isInteractive ? 'interactive' : 'normal');
98
+ store.setMode(isInteractive ? 'interactive' : 'normal');
98
99
  } else if (key.downArrow) {
99
- processStore.selectNextError();
100
+ store.selectNextError();
100
101
  } else if (key.upArrow) {
101
- processStore.selectPrevError();
102
+ store.selectPrevError();
102
103
  } else if (key.return) {
103
- processStore.setMode('errorDetail');
104
+ store.setMode('errorDetail');
104
105
  }
105
106
  } else if (mode === 'errorDetail') {
106
107
  if (key.escape) {
107
- processStore.setMode('errorList');
108
+ store.setMode('errorList');
108
109
  } else if (key.downArrow) {
109
- processStore.selectNextError();
110
+ store.selectNextError();
110
111
  } else if (key.upArrow) {
111
- processStore.selectPrevError();
112
+ store.selectPrevError();
112
113
  }
113
114
  }
114
115
  }, {
@@ -124,7 +125,7 @@ export default function App() {
124
125
  }
125
126
  // Error detail modal
126
127
  if (mode === 'errorDetail') {
127
- const selectedError = processStore.getSelectedError();
128
+ const selectedError = store.getSelectedError();
128
129
  if (selectedError) {
129
130
  return /*#__PURE__*/ _jsx(ErrorDetailModal, {
130
131
  error: selectedError,
@@ -133,17 +134,21 @@ export default function App() {
133
134
  });
134
135
  }
135
136
  // Fallback if no error selected
136
- processStore.setMode('errorList');
137
+ store.setMode('errorList');
137
138
  }
138
139
  // Normal/Interactive view - render in original registration order
139
140
  const showSelection = mode === 'interactive';
140
141
  return /*#__PURE__*/ _jsxs(Box, {
141
142
  flexDirection: "column",
142
143
  children: [
143
- /*#__PURE__*/ _jsx(Text, {
144
- children: header || '(loading...)'
144
+ header && /*#__PURE__*/ _jsxs(_Fragment, {
145
+ children: [
146
+ /*#__PURE__*/ _jsx(Text, {
147
+ children: header
148
+ }),
149
+ /*#__PURE__*/ _jsx(Divider, {})
150
+ ]
145
151
  }),
146
- /*#__PURE__*/ _jsx(Divider, {}),
147
152
  processes.map((item, index)=>/*#__PURE__*/ _jsxs(Box, {
148
153
  flexDirection: "column",
149
154
  children: [
@@ -171,3 +176,12 @@ export default function App() {
171
176
  ]
172
177
  });
173
178
  }
179
+ // Wrapper component that provides store context
180
+ export default function App({ store }) {
181
+ return /*#__PURE__*/ _jsx(StoreContext.Provider, {
182
+ value: store,
183
+ children: /*#__PURE__*/ _jsx(AppContent, {
184
+ store: store
185
+ })
186
+ });
187
+ }
@@ -1 +1 @@
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 // Subscribed state that triggers re-renders\n const header = useSyncExternalStore(processStore.subscribe, processStore.getHeader);\n const showStatusBar = useSyncExternalStore(processStore.subscribe, processStore.getShowStatusBar);\n const isInteractive = useSyncExternalStore(processStore.subscribe, processStore.getIsInteractive);\n\n // Derived state (computed from processes which is already subscribed)\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 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 - always render a line */}\n <Text>{header || '(loading...)'}</Text>\n <Divider />\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","header","getHeader","showStatusBar","getShowStatusBar","isInteractive","getIsInteractive","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","errors","totalErrorLines","selectedError","getSelectedError","error","currentIndex","totalErrors","length","showSelection","flexDirection","map","item","index","isSelected","id","lines","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 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\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\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 // 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();\n }\n } else if (key.upArrow) {\n if (expandedId) {\n store.scrollUp();\n } else {\n store.selectPrev();\n }\n } else if (input === 'j') {\n if (expandedId) {\n store.scrollDown(EXPANDED_MAX_VISIBLE_LINES);\n } else {\n store.selectNext();\n }\n } else if (input === 'k') {\n if (expandedId) {\n store.scrollUp();\n } else {\n store.selectPrev();\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 // 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 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\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","useEffect","useSyncExternalStore","EXPANDED_MAX_VISIBLE_LINES","StoreContext","CompactProcessLine","Divider","ErrorDetailModal","ErrorListModal","ExpandedOutput","StatusBar","AppContent","store","exit","isRawModeSupported","processes","subscribe","getSnapshot","shouldExit","getShouldExit","mode","getMode","selectedIndex","getSelectedIndex","selectedErrorIndex","getSelectedErrorIndex","expandedId","getExpandedId","scrollOffset","getScrollOffset","header","getHeader","showStatusBar","getShowStatusBar","isInteractive","getIsInteractive","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","errors","totalErrorLines","selectedError","getSelectedError","error","currentIndex","totalErrors","length","showSelection","flexDirection","map","item","index","isSelected","id","lines","running","done","errorLines","App","Provider","value"],"mappings":";AAAA,SAASA,GAAG,EAAEC,IAAI,EAAEC,MAAM,EAAEC,QAAQ,EAAEC,QAAQ,QAAQ,MAAM;AAC5D,SAASC,SAAS,EAAEC,oBAAoB,QAAQ,QAAQ;AACxD,SAASC,0BAA0B,QAAQ,kBAAkB;AAE7D,SAASC,YAAY,QAAQ,2BAA2B;AACxD,OAAOC,wBAAwB,0BAA0B;AACzD,OAAOC,aAAa,eAAe;AACnC,OAAOC,sBAAsB,wBAAwB;AACrD,OAAOC,oBAAoB,sBAAsB;AACjD,OAAOC,oBAAoB,sBAAsB;AACjD,OAAOC,eAAe,iBAAiB;AAMvC,SAASC,WAAW,EAAEC,KAAK,EAAY;IACrC,MAAM,EAAEC,IAAI,EAAE,GAAGf;IACjB,MAAM,EAAEgB,kBAAkB,EAAE,GAAGd;IAE/B,2BAA2B;IAC3B,MAAMe,YAAYb,qBAAqBU,MAAMI,SAAS,EAAEJ,MAAMK,WAAW;IACzE,MAAMC,aAAahB,qBAAqBU,MAAMI,SAAS,EAAEJ,MAAMO,aAAa;IAC5E,MAAMC,OAAOlB,qBAAqBU,MAAMI,SAAS,EAAEJ,MAAMS,OAAO;IAChE,MAAMC,gBAAgBpB,qBAAqBU,MAAMI,SAAS,EAAEJ,MAAMW,gBAAgB;IAClF,MAAMC,qBAAqBtB,qBAAqBU,MAAMI,SAAS,EAAEJ,MAAMa,qBAAqB;IAC5F,MAAMC,aAAaxB,qBAAqBU,MAAMI,SAAS,EAAEJ,MAAMe,aAAa;IAC5E,MAAMC,eAAe1B,qBAAqBU,MAAMI,SAAS,EAAEJ,MAAMiB,eAAe;IAEhF,4CAA4C;IAC5C,MAAMC,SAAS5B,qBAAqBU,MAAMI,SAAS,EAAEJ,MAAMmB,SAAS;IACpE,MAAMC,gBAAgB9B,qBAAqBU,MAAMI,SAAS,EAAEJ,MAAMqB,gBAAgB;IAClF,MAAMC,gBAAgBhC,qBAAqBU,MAAMI,SAAS,EAAEJ,MAAMuB,gBAAgB;IAElF,sEAAsE;IACtE,MAAMC,kBAAkBxB,MAAMyB,kBAAkB;IAChD,MAAMC,eAAe1B,MAAM2B,eAAe;IAC1C,MAAMC,YAAY5B,MAAM6B,YAAY;IACpC,MAAMC,aAAa9B,MAAM+B,aAAa;IACtC,MAAMC,iBAAiBhC,MAAMiC,iBAAiB;IAC9C,MAAMC,gBAAgBlC,MAAMkC,aAAa;IAEzC,qBAAqB;IACrB7C,UAAU;QACR,IAAIiB,YAAY;YACdL;QACF;IACF,GAAG;QAACK;QAAYL;KAAK;IAErB,4EAA4E;IAC5EZ,UAAU;QACR,IAAI6C,iBAAiBZ,iBAAiBd,SAAS,UAAU;YACvDR,MAAMmC,OAAO,CAAC;QAChB;IACF,GAAG;QAACD;QAAeZ;QAAed;QAAMR;KAAM;IAE9C,6DAA6D;IAC7Db,SACE,CAACiD,OAAOC;QACN,IAAI7B,SAAS,UAAU;YACrB,IAAI4B,UAAU,OAAON,aAAa,GAAG;gBACnC9B,MAAMmC,OAAO,CAAC;YAChB;QACF,OAAO,IAAI3B,SAAS,eAAe;YACjC,IAAI4B,UAAU,OAAOC,IAAIC,MAAM,EAAE;gBAC/B,IAAIxB,YAAY;oBACdd,MAAMuC,QAAQ;gBAChB,OAAO;oBACLvC,MAAMwC,UAAU,CAAC,KAAO;gBAC1B;YACF,OAAO,IAAIH,IAAII,MAAM,EAAE;gBACrBzC,MAAM0C,YAAY;YACpB,OAAO,IAAIL,IAAIM,SAAS,EAAE;gBACxB,IAAI7B,YAAY;oBACdd,MAAM4C,UAAU,CAACrD;gBACnB,OAAO;oBACLS,MAAM6C,UAAU;gBAClB;YACF,OAAO,IAAIR,IAAIS,OAAO,EAAE;gBACtB,IAAIhC,YAAY;oBACdd,MAAM+C,QAAQ;gBAChB,OAAO;oBACL/C,MAAMgD,UAAU;gBAClB;YACF,OAAO,IAAIZ,UAAU,KAAK;gBACxB,IAAItB,YAAY;oBACdd,MAAM4C,UAAU,CAACrD;gBACnB,OAAO;oBACLS,MAAM6C,UAAU;gBAClB;YACF,OAAO,IAAIT,UAAU,KAAK;gBACxB,IAAItB,YAAY;oBACdd,MAAM+C,QAAQ;gBAChB,OAAO;oBACL/C,MAAMgD,UAAU;gBAClB;YACF,OAAO,IAAIZ,UAAU,OAAON,aAAa,GAAG;gBAC1C9B,MAAMmC,OAAO,CAAC;YAChB;QACF,OAAO,IAAI3B,SAAS,aAAa;YAC/B,IAAI6B,IAAIC,MAAM,EAAE;gBACdtC,MAAMmC,OAAO,CAACb,gBAAgB,gBAAgB;YAChD,OAAO,IAAIe,IAAIM,SAAS,EAAE;gBACxB3C,MAAMiD,eAAe;YACvB,OAAO,IAAIZ,IAAIS,OAAO,EAAE;gBACtB9C,MAAMkD,eAAe;YACvB,OAAO,IAAIb,IAAII,MAAM,EAAE;gBACrBzC,MAAMmC,OAAO,CAAC;YAChB;QACF,OAAO,IAAI3B,SAAS,eAAe;YACjC,IAAI6B,IAAIC,MAAM,EAAE;gBACdtC,MAAMmC,OAAO,CAAC;YAChB,OAAO,IAAIE,IAAIM,SAAS,EAAE;gBACxB3C,MAAMiD,eAAe;YACvB,OAAO,IAAIZ,IAAIS,OAAO,EAAE;gBACtB9C,MAAMkD,eAAe;YACvB;QACF;IACF,GACA;QAAEC,UAAUjD,uBAAuB;IAAK;IAG1C,mBAAmB;IACnB,IAAIM,SAAS,aAAa;QACxB,qBAAO,KAACZ;YAAewD,QAAQ5B;YAAiBd,eAAeE;YAAoByC,iBAAiBrB;;IACtG;IAEA,qBAAqB;IACrB,IAAIxB,SAAS,eAAe;QAC1B,MAAM8C,gBAAgBtD,MAAMuD,gBAAgB;QAC5C,IAAID,eAAe;YACjB,qBAAO,KAAC3D;gBAAiB6D,OAAOF;gBAAeG,cAAc7C;gBAAoB8C,aAAalC,gBAAgBmC,MAAM;;QACtH;QACA,gCAAgC;QAChC3D,MAAMmC,OAAO,CAAC;IAChB;IAEA,kEAAkE;IAClE,MAAMyB,gBAAgBpD,SAAS;IAC/B,qBACE,MAACxB;QAAI6E,eAAc;;YAEhB3C,wBACC;;kCACE,KAACjC;kCAAMiC;;kCACP,KAACxB;;;YAKJS,UAAU2D,GAAG,CAAC,CAACC,MAAMC,sBACpB,MAAChF;oBAAkB6E,eAAc;;sCAC/B,KAACpE;4BAAmBsE,MAAMA;4BAAME,YAAYL,iBAAiBI,UAAUtD;;wBACtEI,eAAeiD,KAAKG,EAAE,kBAAI,KAACrE;4BAAesE,OAAOJ,KAAKI,KAAK;4BAAEnD,cAAcA;;;mBAFpE+C,KAAKG,EAAE;YAOlB9C,iBAAiBjB,UAAUwD,MAAM,GAAG,mBACnC;;kCACE,KAACjE;kCACD,KAACI;wBAAUsE,SAAS1C;wBAAc2C,MAAMzC;wBAAWwB,QAAQtB;wBAAYwC,YAAYtC;;;;;;AAK7F;AAEA,gDAAgD;AAChD,eAAe,SAASuC,IAAI,EAAEvE,KAAK,EAAY;IAC7C,qBACE,KAACR,aAAagF,QAAQ;QAACC,OAAOzE;kBAC5B,cAAA,KAACD;YAAWC,OAAOA;;;AAGzB"}
@@ -31,7 +31,7 @@ import { Box, Text, useStdout } from 'ink';
31
31
  import { memo, useMemo } from 'react';
32
32
  import figures from '../lib/figures.js';
33
33
  import { calculateColumnWidth } from '../lib/format.js';
34
- import { processStore } from '../state/processStore.js';
34
+ import { useStore } from '../state/StoreContext.js';
35
35
  import { LineType } from '../types.js';
36
36
  import Spinner from './Spinner.js';
37
37
  // From: https://github.com/sindresorhus/cli-spinners/blob/00de8fbeee16fa49502fa4f687449f70f2c8ca2c/spinners.json#L2
@@ -66,6 +66,7 @@ function getErrorCount(lines) {
66
66
  return lines.filter((line)=>line.type === LineType.stderr).length;
67
67
  }
68
68
  export default /*#__PURE__*/ memo(function CompactProcessLine({ item, isSelected = false }) {
69
+ const store = useStore();
69
70
  const { stdout } = useStdout();
70
71
  const terminalWidth = (stdout === null || stdout === void 0 ? void 0 : stdout.columns) || 80;
71
72
  const { group, title, state, lines } = item;
@@ -74,7 +75,7 @@ export default /*#__PURE__*/ memo(function CompactProcessLine({ item, isSelected
74
75
  const displayName = group || title;
75
76
  // Calculate widths - use dynamic column width based on longest name
76
77
  const iconWidth = 2; // icon + space
77
- const maxGroupLength = processStore.getMaxGroupLength();
78
+ const maxGroupLength = store.getMaxGroupLength();
78
79
  const nameColumnWidth = calculateColumnWidth('max', terminalWidth, maxGroupLength);
79
80
  const gap = 1; // space between name and status
80
81
  const statusWidth = terminalWidth - iconWidth - nameColumnWidth - gap;
@@ -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 { 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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"}
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 { useStore } from '../state/StoreContext.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 store = useStore();\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 = store.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","useStore","LineType","Spinner","SPINNER","interval","frames","truncate","str","maxLength","length","slice","getLastOutputLine","lines","i","text","getErrorCount","filter","line","type","stderr","CompactProcessLine","item","isSelected","store","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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"}
@@ -1,4 +1,4 @@
1
1
  export { default as figures } from './lib/figures.js';
2
2
  export { default as formatArguments } from './lib/formatArguments.js';
3
3
  export * from './types.js';
4
- export default undefined;
4
+ export const createSession = undefined;
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/index-cjs.ts"],"sourcesContent":["export { default as figures } from './lib/figures.ts';\nexport { default as formatArguments } from './lib/formatArguments.ts';\nexport * from './types.ts';\n\nimport type { default as spawnTerminal } from './spawnTerminal.ts';\nexport default undefined as typeof spawnTerminal;\n"],"names":["default","figures","formatArguments","undefined"],"mappings":"AAAA,SAASA,WAAWC,OAAO,QAAQ,mBAAmB;AACtD,SAASD,WAAWE,eAAe,QAAQ,2BAA2B;AACtE,cAAc,aAAa;AAG3B,eAAeC,UAAkC"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/index-cjs.ts"],"sourcesContent":["export { default as figures } from './lib/figures.ts';\nexport { default as formatArguments } from './lib/formatArguments.ts';\nexport * from './types.ts';\n\nimport type { createSession as createSessionType, Session } from './session.ts';\nexport type { Session };\nexport const createSession = undefined as typeof createSessionType;\n"],"names":["default","figures","formatArguments","createSession","undefined"],"mappings":"AAAA,SAASA,WAAWC,OAAO,QAAQ,mBAAmB;AACtD,SAASD,WAAWE,eAAe,QAAQ,2BAA2B;AACtE,cAAc,aAAa;AAI3B,OAAO,MAAMC,gBAAgBC,UAAsC"}
@@ -2,5 +2,5 @@ export { default as figures } from './lib/figures.js';
2
2
  export { default as formatArguments } from './lib/formatArguments.js';
3
3
  export * from './types.js';
4
4
  const major = +process.versions.node.split('.')[0];
5
- import { default as spawnTerminal } from './spawnTerminal.js';
6
- export default major > 18 ? spawnTerminal : undefined;
5
+ import { createSession as createSessionImpl } from './session.js';
6
+ export const createSession = major > 18 ? createSessionImpl : undefined;
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/index-esm.ts"],"sourcesContent":["export { default as figures } from './lib/figures.ts';\nexport { default as formatArguments } from './lib/formatArguments.ts';\nexport * from './types.ts';\n\nconst major = +process.versions.node.split('.')[0];\n\nimport { default as spawnTerminal } from './spawnTerminal.ts';\nexport default major > 18 ? spawnTerminal : (undefined as typeof spawnTerminal);\n"],"names":["default","figures","formatArguments","major","process","versions","node","split","spawnTerminal","undefined"],"mappings":"AAAA,SAASA,WAAWC,OAAO,QAAQ,mBAAmB;AACtD,SAASD,WAAWE,eAAe,QAAQ,2BAA2B;AACtE,cAAc,aAAa;AAE3B,MAAMC,QAAQ,CAACC,QAAQC,QAAQ,CAACC,IAAI,CAACC,KAAK,CAAC,IAAI,CAAC,EAAE;AAElD,SAASP,WAAWQ,aAAa,QAAQ,qBAAqB;AAC9D,eAAeL,QAAQ,KAAKK,gBAAiBC,UAAmC"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/index-esm.ts"],"sourcesContent":["export { default as figures } from './lib/figures.ts';\nexport { default as formatArguments } from './lib/formatArguments.ts';\nexport * from './types.ts';\n\nconst major = +process.versions.node.split('.')[0];\n\nimport { createSession as createSessionImpl, type Session } from './session.ts';\nexport type { Session };\nexport const createSession = major > 18 ? createSessionImpl : (undefined as typeof createSessionImpl);\n"],"names":["default","figures","formatArguments","major","process","versions","node","split","createSession","createSessionImpl","undefined"],"mappings":"AAAA,SAASA,WAAWC,OAAO,QAAQ,mBAAmB;AACtD,SAASD,WAAWE,eAAe,QAAQ,2BAA2B;AACtE,cAAc,aAAa;AAE3B,MAAMC,QAAQ,CAACC,QAAQC,QAAQ,CAACC,IAAI,CAACC,KAAK,CAAC,IAAI,CAAC,EAAE;AAElD,SAASC,iBAAiBC,iBAAiB,QAAsB,eAAe;AAEhF,OAAO,MAAMD,gBAAgBL,QAAQ,KAAKM,oBAAqBC,UAAuC"}
@@ -0,0 +1,218 @@
1
+ function _object_without_properties(source, excluded) {
2
+ if (source == null) return {};
3
+ var target = _object_without_properties_loose(source, excluded);
4
+ var key, i;
5
+ if (Object.getOwnPropertySymbols) {
6
+ var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
7
+ for(i = 0; i < sourceSymbolKeys.length; i++){
8
+ key = sourceSymbolKeys[i];
9
+ if (excluded.indexOf(key) >= 0) continue;
10
+ if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
11
+ target[key] = source[key];
12
+ }
13
+ }
14
+ return target;
15
+ }
16
+ function _object_without_properties_loose(source, excluded) {
17
+ if (source == null) return {};
18
+ var target = {};
19
+ var sourceKeys = Object.keys(source);
20
+ var key, i;
21
+ for(i = 0; i < sourceKeys.length; i++){
22
+ key = sourceKeys[i];
23
+ if (excluded.indexOf(key) >= 0) continue;
24
+ target[key] = source[key];
25
+ }
26
+ return target;
27
+ }
28
+ import { jsx as _jsx } from "react/jsx-runtime";
29
+ import spawn, { crossSpawn } from 'cross-spawn-cb';
30
+ import crypto from 'crypto';
31
+ import { render } from 'ink';
32
+ import oo from 'on-one';
33
+ import Queue from 'queue-cb';
34
+ import App from './components/App.js';
35
+ import { DEFAULT_MAX_FPS } from './constants.js';
36
+ import addLines from './lib/addLines.js';
37
+ import concatWritable from './lib/concatWritable.js';
38
+ import formatArguments from './lib/formatArguments.js';
39
+ import { ProcessStore } from './state/processStore.js';
40
+ import { LineType } from './types.js';
41
+ class SessionImpl {
42
+ spawn(command, args, spawnOptions, options, callback) {
43
+ if (this.closed) {
44
+ throw new Error('Session is closed');
45
+ }
46
+ const { encoding, stdio } = spawnOptions, csOptions = _object_without_properties(spawnOptions, [
47
+ "encoding",
48
+ "stdio"
49
+ ]);
50
+ if (stdio === 'inherit') {
51
+ this.runningCount++;
52
+ const id = crypto.randomUUID();
53
+ this.store.addProcess({
54
+ id,
55
+ title: [
56
+ command
57
+ ].concat(formatArguments(args)).join(' '),
58
+ state: 'running',
59
+ lines: [],
60
+ group: options.group,
61
+ expanded: options.expanded
62
+ });
63
+ const cp = crossSpawn(command, args, csOptions);
64
+ const outputs = {
65
+ stdout: null,
66
+ stderr: null
67
+ };
68
+ const queue = new Queue();
69
+ if (cp.stdout) {
70
+ outputs.stdout = addLines((lines)=>{
71
+ this.store.appendLines(id, lines.map((text)=>({
72
+ type: LineType.stdout,
73
+ text
74
+ })));
75
+ });
76
+ queue.defer(oo.bind(null, cp.stdout.pipe(outputs.stdout), [
77
+ 'error',
78
+ 'end',
79
+ 'close',
80
+ 'finish'
81
+ ]));
82
+ }
83
+ if (cp.stderr) {
84
+ outputs.stderr = addLines((lines)=>{
85
+ this.store.appendLines(id, lines.map((text)=>({
86
+ type: LineType.stderr,
87
+ text
88
+ })));
89
+ });
90
+ queue.defer(oo.bind(null, cp.stderr.pipe(outputs.stderr), [
91
+ 'error',
92
+ 'end',
93
+ 'close',
94
+ 'finish'
95
+ ]));
96
+ }
97
+ queue.defer(spawn.worker.bind(null, cp, csOptions));
98
+ queue.await((err)=>{
99
+ const res = err ? err : {};
100
+ res.stdout = outputs.stdout ? outputs.stdout.output : null;
101
+ res.stderr = outputs.stderr ? outputs.stderr.output : null;
102
+ res.output = [
103
+ res.stdout,
104
+ res.stderr,
105
+ null
106
+ ];
107
+ this.store.updateProcess(id, {
108
+ state: err ? 'error' : 'success'
109
+ });
110
+ this.onProcessComplete();
111
+ err ? callback(err) : callback(null, res);
112
+ });
113
+ } else {
114
+ // Non-inherit mode: collect output but don't display in UI
115
+ const cp = crossSpawn(command, args, csOptions);
116
+ const outputs = {
117
+ stdout: null,
118
+ stderr: null
119
+ };
120
+ const queue = new Queue();
121
+ if (cp.stdout) {
122
+ outputs.stdout = concatWritable((output)=>{
123
+ outputs.stdout.output = output.toString(encoding || 'utf8');
124
+ });
125
+ queue.defer(oo.bind(null, cp.stdout.pipe(outputs.stdout), [
126
+ 'error',
127
+ 'end',
128
+ 'close',
129
+ 'finish'
130
+ ]));
131
+ }
132
+ if (cp.stderr) {
133
+ outputs.stderr = concatWritable((output)=>{
134
+ outputs.stderr.output = output.toString(encoding || 'utf8');
135
+ });
136
+ queue.defer(oo.bind(null, cp.stderr.pipe(outputs.stderr), [
137
+ 'error',
138
+ 'end',
139
+ 'close',
140
+ 'finish'
141
+ ]));
142
+ }
143
+ queue.defer(spawn.worker.bind(null, cp, csOptions));
144
+ queue.await((err)=>{
145
+ const res = err ? err : {};
146
+ res.stdout = outputs.stdout ? outputs.stdout.output : null;
147
+ res.stderr = outputs.stderr ? outputs.stderr.output : null;
148
+ res.output = [
149
+ res.stdout,
150
+ res.stderr,
151
+ null
152
+ ];
153
+ err ? callback(err) : callback(null, res);
154
+ });
155
+ }
156
+ }
157
+ close() {
158
+ if (this.closed) return;
159
+ this.closed = true;
160
+ this.cleanup();
161
+ }
162
+ waitAndClose(callback) {
163
+ if (this.closed) {
164
+ callback === null || callback === void 0 ? void 0 : callback();
165
+ return;
166
+ }
167
+ if (this.runningCount === 0) {
168
+ this.close();
169
+ callback === null || callback === void 0 ? void 0 : callback();
170
+ } else {
171
+ if (callback) this.waitCallbacks.push(callback);
172
+ // Will close when runningCount hits 0
173
+ }
174
+ }
175
+ onProcessComplete() {
176
+ this.runningCount--;
177
+ if (this.runningCount === 0 && this.waitCallbacks.length > 0) {
178
+ this.close();
179
+ for (const cb of this.waitCallbacks)cb();
180
+ this.waitCallbacks = [];
181
+ }
182
+ }
183
+ cleanup() {
184
+ // Signal exit to React component
185
+ this.store.signalExit(()=>{
186
+ this.store.reset();
187
+ process.stdout.write('\x1b[?25h'); // show cursor
188
+ });
189
+ // Wait for Ink to finish
190
+ if (this.inkApp) {
191
+ this.inkApp.waitUntilExit().then(()=>{
192
+ const cb = this.store.getExitCallback();
193
+ cb === null || cb === void 0 ? void 0 : cb();
194
+ }).catch(()=>{
195
+ const cb = this.store.getExitCallback();
196
+ cb === null || cb === void 0 ? void 0 : cb();
197
+ });
198
+ this.inkApp = null;
199
+ }
200
+ }
201
+ constructor(options = {}){
202
+ this.inkApp = null;
203
+ this.runningCount = 0;
204
+ this.closed = false;
205
+ this.waitCallbacks = [];
206
+ this.store = new ProcessStore(options);
207
+ // Render Ink app immediately
208
+ this.inkApp = render(/*#__PURE__*/ _jsx(App, {
209
+ store: this.store
210
+ }), {
211
+ incrementalRendering: true,
212
+ maxFps: DEFAULT_MAX_FPS
213
+ });
214
+ }
215
+ }
216
+ export function createSession(options = {}) {
217
+ return new SessionImpl(options);
218
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/session.tsx"],"sourcesContent":["import spawn, { crossSpawn, type SpawnResult } from 'cross-spawn-cb';\nimport crypto from 'crypto';\nimport { render } from 'ink';\nimport oo from 'on-one';\nimport Queue from 'queue-cb';\n\nimport App from './components/App.ts';\nimport { DEFAULT_MAX_FPS } from './constants.ts';\nimport addLines from './lib/addLines.ts';\nimport concatWritable from './lib/concatWritable.ts';\nimport formatArguments from './lib/formatArguments.ts';\nimport { ProcessStore } from './state/processStore.ts';\nimport type { ProcessOptions, SessionOptions, SpawnError, SpawnOptions, TerminalCallback } from './types.ts';\nimport { LineType } from './types.ts';\n\nexport interface Session {\n spawn(command: string, args: string[], spawnOptions: SpawnOptions, options: ProcessOptions, callback: TerminalCallback): void;\n close(): void;\n waitAndClose(callback?: () => void): void;\n}\n\nclass SessionImpl implements Session {\n private store: ProcessStore;\n private inkApp: ReturnType<typeof render> | null = null;\n private runningCount = 0;\n private closed = false;\n private waitCallbacks: (() => void)[] = [];\n\n constructor(options: SessionOptions = {}) {\n this.store = new ProcessStore(options);\n\n // Render Ink app immediately\n this.inkApp = render(<App store={this.store} />, {\n incrementalRendering: true,\n maxFps: DEFAULT_MAX_FPS,\n });\n }\n\n spawn(command: string, args: string[], spawnOptions: SpawnOptions, options: ProcessOptions, callback: TerminalCallback): void {\n if (this.closed) {\n throw new Error('Session is closed');\n }\n\n const { encoding, stdio, ...csOptions } = spawnOptions;\n\n if (stdio === 'inherit') {\n this.runningCount++;\n const id = crypto.randomUUID();\n this.store.addProcess({\n id,\n title: [command].concat(formatArguments(args)).join(' '),\n state: 'running',\n lines: [],\n group: options.group,\n expanded: options.expanded,\n });\n\n const cp = crossSpawn(command, args, csOptions);\n const outputs = { stdout: null as ReturnType<typeof addLines> | null, stderr: null as ReturnType<typeof addLines> | null };\n\n const queue = new Queue();\n if (cp.stdout) {\n outputs.stdout = addLines((lines) => {\n this.store.appendLines(\n id,\n lines.map((text) => ({ type: LineType.stdout, text }))\n );\n });\n queue.defer(oo.bind(null, cp.stdout.pipe(outputs.stdout), ['error', 'end', 'close', 'finish']));\n }\n if (cp.stderr) {\n outputs.stderr = addLines((lines) => {\n this.store.appendLines(\n id,\n lines.map((text) => ({ type: LineType.stderr, text }))\n );\n });\n queue.defer(oo.bind(null, cp.stderr.pipe(outputs.stderr), ['error', 'end', 'close', 'finish']));\n }\n queue.defer(spawn.worker.bind(null, cp, csOptions));\n queue.await((err?: SpawnError) => {\n const res = (err ? err : {}) as SpawnResult;\n res.stdout = outputs.stdout ? (outputs.stdout as unknown as { output: string }).output : null;\n res.stderr = outputs.stderr ? (outputs.stderr as unknown as { output: string }).output : null;\n res.output = [res.stdout, res.stderr, null];\n this.store.updateProcess(id, { state: err ? 'error' : 'success' });\n\n this.onProcessComplete();\n err ? callback(err) : callback(null, res);\n });\n } else {\n // Non-inherit mode: collect output but don't display in UI\n const cp = crossSpawn(command, args, csOptions);\n const outputs = { stdout: null as ReturnType<typeof concatWritable> | null, stderr: null as ReturnType<typeof concatWritable> | null };\n\n const queue = new Queue();\n if (cp.stdout) {\n outputs.stdout = concatWritable((output) => {\n (outputs.stdout as unknown as { output: string }).output = output.toString(encoding || 'utf8');\n });\n queue.defer(oo.bind(null, cp.stdout.pipe(outputs.stdout), ['error', 'end', 'close', 'finish']));\n }\n if (cp.stderr) {\n outputs.stderr = concatWritable((output) => {\n (outputs.stderr as unknown as { output: string }).output = output.toString(encoding || 'utf8');\n });\n queue.defer(oo.bind(null, cp.stderr.pipe(outputs.stderr), ['error', 'end', 'close', 'finish']));\n }\n queue.defer(spawn.worker.bind(null, cp, csOptions));\n queue.await((err?: SpawnError) => {\n const res = (err ? err : {}) as SpawnResult;\n res.stdout = outputs.stdout ? (outputs.stdout as unknown as { output: string }).output : null;\n res.stderr = outputs.stderr ? (outputs.stderr as unknown as { output: string }).output : null;\n res.output = [res.stdout, res.stderr, null];\n err ? callback(err) : callback(null, res);\n });\n }\n }\n\n close(): void {\n if (this.closed) return;\n this.closed = true;\n this.cleanup();\n }\n\n waitAndClose(callback?: () => void): void {\n if (this.closed) {\n callback?.();\n return;\n }\n\n if (this.runningCount === 0) {\n this.close();\n callback?.();\n } else {\n if (callback) this.waitCallbacks.push(callback);\n // Will close when runningCount hits 0\n }\n }\n\n private onProcessComplete(): void {\n this.runningCount--;\n if (this.runningCount === 0 && this.waitCallbacks.length > 0) {\n this.close();\n for (const cb of this.waitCallbacks) cb();\n this.waitCallbacks = [];\n }\n }\n\n private cleanup(): void {\n // Signal exit to React component\n this.store.signalExit(() => {\n this.store.reset();\n process.stdout.write('\\x1b[?25h'); // show cursor\n });\n\n // Wait for Ink to finish\n if (this.inkApp) {\n this.inkApp\n .waitUntilExit()\n .then(() => {\n const cb = this.store.getExitCallback();\n cb?.();\n })\n .catch(() => {\n const cb = this.store.getExitCallback();\n cb?.();\n });\n this.inkApp = null;\n }\n }\n}\n\nexport function createSession(options: SessionOptions = {}): Session {\n return new SessionImpl(options);\n}\n"],"names":["spawn","crossSpawn","crypto","render","oo","Queue","App","DEFAULT_MAX_FPS","addLines","concatWritable","formatArguments","ProcessStore","LineType","SessionImpl","command","args","spawnOptions","options","callback","closed","Error","encoding","stdio","csOptions","runningCount","id","randomUUID","store","addProcess","title","concat","join","state","lines","group","expanded","cp","outputs","stdout","stderr","queue","appendLines","map","text","type","defer","bind","pipe","worker","await","err","res","output","updateProcess","onProcessComplete","toString","close","cleanup","waitAndClose","waitCallbacks","push","length","cb","signalExit","reset","process","write","inkApp","waitUntilExit","then","getExitCallback","catch","incrementalRendering","maxFps","createSession"],"mappings":"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"}
@@ -1 +1,6 @@
1
- export default function App(): React.JSX.Element;
1
+ import type { ProcessStore } from '../state/processStore.js';
2
+ interface AppProps {
3
+ store: ProcessStore;
4
+ }
5
+ export default function App({ store }: AppProps): React.JSX.Element;
6
+ export {};
@@ -1,6 +1,6 @@
1
1
  export { default as figures } from './lib/figures.js';
2
2
  export { default as formatArguments } from './lib/formatArguments.js';
3
3
  export * from './types.js';
4
- import type { default as spawnTerminal } from './spawnTerminal.js';
5
- declare const _default: typeof spawnTerminal;
6
- export default _default;
4
+ import type { createSession as createSessionType, Session } from './session.js';
5
+ export type { Session };
6
+ export declare const createSession: typeof createSessionType;
@@ -1,6 +1,6 @@
1
1
  export { default as figures } from './lib/figures.js';
2
2
  export { default as formatArguments } from './lib/formatArguments.js';
3
3
  export * from './types.js';
4
- import { default as spawnTerminal } from './spawnTerminal.js';
5
- declare const _default: typeof spawnTerminal;
6
- export default _default;
4
+ import { createSession as createSessionImpl, type Session } from './session.js';
5
+ export type { Session };
6
+ export declare const createSession: typeof createSessionImpl;