spawn-term 3.0.1 → 3.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/components/App.js +25 -6
- package/dist/cjs/components/App.js.map +1 -1
- package/dist/cjs/components/ChildProcess.js +2 -17
- package/dist/cjs/components/ChildProcess.js.map +1 -1
- package/dist/cjs/components/CompactProcessLine.js +27 -36
- package/dist/cjs/components/CompactProcessLine.js.map +1 -1
- package/dist/cjs/components/StatusBar.js +4 -19
- package/dist/cjs/components/StatusBar.js.map +1 -1
- package/dist/cjs/constants.js +18 -0
- package/dist/cjs/constants.js.map +1 -1
- package/dist/cjs/createSessionWrapper.js +101 -0
- package/dist/cjs/createSessionWrapper.js.map +1 -0
- package/dist/cjs/index-esm.js +2 -2
- package/dist/cjs/index-esm.js.map +1 -1
- package/dist/cjs/lib/clipText.js +56 -0
- package/dist/cjs/lib/clipText.js.map +1 -0
- package/dist/cjs/session.js +26 -2
- package/dist/cjs/session.js.map +1 -1
- package/dist/cjs/src/constants.d.ts +4 -0
- package/dist/cjs/src/createSessionWrapper.d.ts +7 -0
- package/dist/cjs/src/index-esm.d.ts +2 -2
- package/dist/cjs/src/lib/clipText.d.ts +9 -0
- package/dist/cjs/src/state/processStore.d.ts +5 -2
- package/dist/cjs/state/processStore.js +20 -2
- package/dist/cjs/state/processStore.js.map +1 -1
- package/dist/esm/components/App.js +30 -9
- package/dist/esm/components/App.js.map +1 -1
- package/dist/esm/components/ChildProcess.js +1 -16
- package/dist/esm/components/ChildProcess.js.map +1 -1
- package/dist/esm/components/CompactProcessLine.js +26 -35
- package/dist/esm/components/CompactProcessLine.js.map +1 -1
- package/dist/esm/components/StatusBar.js +3 -18
- package/dist/esm/components/StatusBar.js.map +1 -1
- package/dist/esm/constants.js +16 -0
- package/dist/esm/constants.js.map +1 -1
- package/dist/esm/createSessionWrapper.js +43 -0
- package/dist/esm/createSessionWrapper.js.map +1 -0
- package/dist/esm/index-esm.js +1 -1
- package/dist/esm/index-esm.js.map +1 -1
- package/dist/esm/lib/clipText.js +37 -0
- package/dist/esm/lib/clipText.js.map +1 -0
- package/dist/esm/session.js +24 -2
- package/dist/esm/session.js.map +1 -1
- package/dist/esm/src/constants.d.ts +4 -0
- package/dist/esm/src/createSessionWrapper.d.ts +7 -0
- package/dist/esm/src/index-esm.d.ts +2 -2
- package/dist/esm/src/lib/clipText.d.ts +9 -0
- package/dist/esm/src/state/processStore.d.ts +5 -2
- package/dist/esm/state/processStore.js +18 -2
- package/dist/esm/state/processStore.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/components/ChildProcess.tsx"],"sourcesContent":["import { Box, Text } from 'ink';\nimport { memo, useMemo } from 'react';\nimport ansiRegex from '../lib/ansiRegex.ts';\nimport figures from '../lib/figures.ts';\nimport type { ChildProcess as ChildProcessT, Line, State } from '../types.ts';\nimport { LineType } from '../types.ts';\nimport Spinner from './Spinner.ts';\n\nconst REGEX_ANSI = ansiRegex();\nconst BLANK_LINE = { type: LineType.stdout, text: '' };\n\
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/components/ChildProcess.tsx"],"sourcesContent":["import { Box, Text } from 'ink';\nimport { memo, useMemo } from 'react';\nimport { SPINNER } from '../constants.ts';\nimport ansiRegex from '../lib/ansiRegex.ts';\nimport figures from '../lib/figures.ts';\nimport type { ChildProcess as ChildProcessT, Line, State } from '../types.ts';\nimport { LineType } from '../types.ts';\nimport Spinner from './Spinner.ts';\n\nconst REGEX_ANSI = ansiRegex();\nconst BLANK_LINE = { type: LineType.stdout, text: '' };\n\nconst ICONS = {\n error: <Text color=\"red\">{figures.cross}</Text>,\n success: <Text color=\"green\">{figures.tick}</Text>,\n running: <Spinner {...SPINNER} />,\n};\n\ntype ItemProps = {\n item: ChildProcessT;\n};\n\ntype HeaderProps = {\n group?: string;\n title: string;\n state: State;\n};\n\nconst Header = memo(\n function Header({ group, title, state }: HeaderProps) {\n const icon = ICONS[state];\n\n return (\n <Box>\n {icon}\n {group && <Text bold>{`${group}${figures.pointer} `}</Text>}\n <Text>{title}</Text>\n </Box>\n );\n },\n (a, b) => a.group === b.group && a.title === b.title && a.state === b.state\n);\n\ntype RunningSummaryProps = {\n line: Line;\n};\n\nconst RunningSummary = memo(function RunningSummary({ line }: RunningSummaryProps) {\n return (\n <Box marginLeft={2}>\n <Text color=\"gray\">{line.text.replace(REGEX_ANSI, '')}</Text>\n </Box>\n );\n});\n\ntype LinesProps = {\n lines: Line[];\n};\n\nconst renderLine = (line, index) => {\n return (\n <Box key={index} minHeight={1}>\n <Text>{line.text}</Text>\n </Box>\n );\n};\n\nconst Lines = memo(function Lines({ lines }: LinesProps) {\n return (\n <Box flexDirection=\"column\" marginLeft={2}>\n {lines.map(renderLine)}\n </Box>\n );\n});\n\nconst Expanded = memo(function Expanded({ item }: ItemProps) {\n const { lines } = item;\n\n return (\n <Box flexDirection=\"column\">\n <Header group={item.group} title={item.title} state={item.state} />\n <Lines lines={lines} />\n </Box>\n );\n});\n\nconst Contracted = memo(function Contracted({ item }: ItemProps) {\n const { state, lines } = item;\n\n // remove ansi codes when displaying single lines\n const errors = useMemo(() => lines.filter((line) => line.type === LineType.stderr), [lines]);\n const summary = useMemo(() => lines.filter((line) => line.text.length > 0 && errors.indexOf(line) < 0).pop(), [lines, errors]);\n\n return (\n <Box flexDirection=\"column\">\n <Header group={item.group} title={item.title} state={item.state} />\n {state === 'running' && <RunningSummary line={summary || BLANK_LINE} />}\n {errors.length > 0 && <Lines lines={errors} />}\n </Box>\n );\n});\n\nexport default memo(function ChildProcess({ item }: ItemProps) {\n const { expanded } = item;\n return expanded ? <Expanded item={item} /> : <Contracted item={item} />;\n});\n"],"names":["Box","Text","memo","useMemo","SPINNER","ansiRegex","figures","LineType","Spinner","REGEX_ANSI","BLANK_LINE","type","stdout","text","ICONS","error","color","cross","success","tick","running","Header","group","title","state","icon","bold","pointer","a","b","RunningSummary","line","marginLeft","replace","renderLine","index","minHeight","Lines","lines","flexDirection","map","Expanded","item","Contracted","errors","filter","stderr","summary","length","indexOf","pop","ChildProcess","expanded"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAASA,GAAG,EAAEC,IAAI,QAAQ,MAAM;AAChC,SAASC,IAAI,EAAEC,OAAO,QAAQ,QAAQ;AACtC,SAASC,OAAO,QAAQ,kBAAkB;AAC1C,OAAOC,eAAe,sBAAsB;AAC5C,OAAOC,aAAa,oBAAoB;AAExC,SAASC,QAAQ,QAAQ,cAAc;AACvC,OAAOC,aAAa,eAAe;AAEnC,MAAMC,aAAaJ;AACnB,MAAMK,aAAa;IAAEC,MAAMJ,SAASK,MAAM;IAAEC,MAAM;AAAG;AAErD,MAAMC,QAAQ;IACZC,qBAAO,KAACd;QAAKe,OAAM;kBAAOV,QAAQW,KAAK;;IACvCC,uBAAS,KAACjB;QAAKe,OAAM;kBAASV,QAAQa,IAAI;;IAC1CC,uBAAS,KAACZ,4BAAYJ;AACxB;AAYA,MAAMiB,uBAASnB,KACb,SAASmB,OAAO,EAAEC,KAAK,EAAEC,KAAK,EAAEC,KAAK,EAAe;IAClD,MAAMC,OAAOX,KAAK,CAACU,MAAM;IAEzB,qBACE,MAACxB;;YACEyB;YACAH,uBAAS,KAACrB;gBAAKyB,IAAI;0BAAE,GAAGJ,QAAQhB,QAAQqB,OAAO,CAAC,CAAC,CAAC;;0BACnD,KAAC1B;0BAAMsB;;;;AAGb,GACA,CAACK,GAAGC,IAAMD,EAAEN,KAAK,KAAKO,EAAEP,KAAK,IAAIM,EAAEL,KAAK,KAAKM,EAAEN,KAAK,IAAIK,EAAEJ,KAAK,KAAKK,EAAEL,KAAK;AAO7E,MAAMM,+BAAiB5B,KAAK,SAAS4B,eAAe,EAAEC,IAAI,EAAuB;IAC/E,qBACE,KAAC/B;QAAIgC,YAAY;kBACf,cAAA,KAAC/B;YAAKe,OAAM;sBAAQe,KAAKlB,IAAI,CAACoB,OAAO,CAACxB,YAAY;;;AAGxD;AAMA,MAAMyB,aAAa,CAACH,MAAMI;IACxB,qBACE,KAACnC;QAAgBoC,WAAW;kBAC1B,cAAA,KAACnC;sBAAM8B,KAAKlB,IAAI;;OADRsB;AAId;AAEA,MAAME,sBAAQnC,KAAK,SAASmC,MAAM,EAAEC,KAAK,EAAc;IACrD,qBACE,KAACtC;QAAIuC,eAAc;QAASP,YAAY;kBACrCM,MAAME,GAAG,CAACN;;AAGjB;AAEA,MAAMO,yBAAWvC,KAAK,SAASuC,SAAS,EAAEC,IAAI,EAAa;IACzD,MAAM,EAAEJ,KAAK,EAAE,GAAGI;IAElB,qBACE,MAAC1C;QAAIuC,eAAc;;0BACjB,KAAClB;gBAAOC,OAAOoB,KAAKpB,KAAK;gBAAEC,OAAOmB,KAAKnB,KAAK;gBAAEC,OAAOkB,KAAKlB,KAAK;;0BAC/D,KAACa;gBAAMC,OAAOA;;;;AAGpB;AAEA,MAAMK,2BAAazC,KAAK,SAASyC,WAAW,EAAED,IAAI,EAAa;IAC7D,MAAM,EAAElB,KAAK,EAAEc,KAAK,EAAE,GAAGI;IAEzB,iDAAiD;IACjD,MAAME,SAASzC,QAAQ,IAAMmC,MAAMO,MAAM,CAAC,CAACd,OAASA,KAAKpB,IAAI,KAAKJ,SAASuC,MAAM,GAAG;QAACR;KAAM;IAC3F,MAAMS,UAAU5C,QAAQ,IAAMmC,MAAMO,MAAM,CAAC,CAACd,OAASA,KAAKlB,IAAI,CAACmC,MAAM,GAAG,KAAKJ,OAAOK,OAAO,CAAClB,QAAQ,GAAGmB,GAAG,IAAI;QAACZ;QAAOM;KAAO;IAE7H,qBACE,MAAC5C;QAAIuC,eAAc;;0BACjB,KAAClB;gBAAOC,OAAOoB,KAAKpB,KAAK;gBAAEC,OAAOmB,KAAKnB,KAAK;gBAAEC,OAAOkB,KAAKlB,KAAK;;YAC9DA,UAAU,2BAAa,KAACM;gBAAeC,MAAMgB,WAAWrC;;YACxDkC,OAAOI,MAAM,GAAG,mBAAK,KAACX;gBAAMC,OAAOM;;;;AAG1C;AAEA,6BAAe1C,KAAK,SAASiD,aAAa,EAAET,IAAI,EAAa;IAC3D,MAAM,EAAEU,QAAQ,EAAE,GAAGV;IACrB,OAAOU,yBAAW,KAACX;QAASC,MAAMA;uBAAW,KAACC;QAAWD,MAAMA;;AACjE,GAAG"}
|
|
@@ -29,31 +29,13 @@ function _object_spread(target) {
|
|
|
29
29
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
30
30
|
import { Box, Text, useStdout } from 'ink';
|
|
31
31
|
import { memo, useMemo } from 'react';
|
|
32
|
+
import { SPINNER } from '../constants.js';
|
|
33
|
+
import { clipText } from '../lib/clipText.js';
|
|
32
34
|
import figures from '../lib/figures.js';
|
|
33
35
|
import { calculateColumnWidth } from '../lib/format.js';
|
|
34
36
|
import { useStore } from '../state/StoreContext.js';
|
|
35
37
|
import { LineType } from '../types.js';
|
|
36
38
|
import Spinner from './Spinner.js';
|
|
37
|
-
// From: https://github.com/sindresorhus/cli-spinners/blob/00de8fbeee16fa49502fa4f687449f70f2c8ca2c/spinners.json#L2
|
|
38
|
-
const SPINNER = {
|
|
39
|
-
interval: 80,
|
|
40
|
-
frames: [
|
|
41
|
-
'⠋',
|
|
42
|
-
'⠙',
|
|
43
|
-
'⠹',
|
|
44
|
-
'⠸',
|
|
45
|
-
'⠼',
|
|
46
|
-
'⠴',
|
|
47
|
-
'⠦',
|
|
48
|
-
'⠧',
|
|
49
|
-
'⠇',
|
|
50
|
-
'⠏'
|
|
51
|
-
]
|
|
52
|
-
};
|
|
53
|
-
function truncate(str, maxLength) {
|
|
54
|
-
if (str.length <= maxLength) return str;
|
|
55
|
-
return `${str.slice(0, maxLength - 1)}…`;
|
|
56
|
-
}
|
|
57
39
|
function getLastOutputLine(lines) {
|
|
58
40
|
for(let i = lines.length - 1; i >= 0; i--){
|
|
59
41
|
if (lines[i].text.length > 0) {
|
|
@@ -74,22 +56,24 @@ export default /*#__PURE__*/ memo(function CompactProcessLine({ item, isSelected
|
|
|
74
56
|
// Display name: prefer group, fall back to title
|
|
75
57
|
const displayName = group || title;
|
|
76
58
|
// Calculate widths - use dynamic column width based on longest name
|
|
59
|
+
const selectionWidth = 1; // selection indicator
|
|
77
60
|
const iconWidth = 2; // icon + space
|
|
78
61
|
const maxGroupLength = store.getMaxGroupLength();
|
|
79
62
|
const nameColumnWidth = calculateColumnWidth('max', terminalWidth, maxGroupLength);
|
|
80
63
|
const gap = 1; // space between name and status
|
|
81
|
-
const statusWidth = terminalWidth - iconWidth - nameColumnWidth - gap;
|
|
82
|
-
//
|
|
83
|
-
const
|
|
84
|
-
// Status text based on state
|
|
64
|
+
const statusWidth = Math.max(0, terminalWidth - selectionWidth - iconWidth - nameColumnWidth - gap);
|
|
65
|
+
// Clip name to column width and pad
|
|
66
|
+
const clippedName = clipText(displayName, nameColumnWidth).padEnd(nameColumnWidth);
|
|
67
|
+
// Status text based on state - clip to available width
|
|
85
68
|
const statusText = useMemo(()=>{
|
|
86
69
|
if (state === 'running') {
|
|
87
70
|
const lastLine = getLastOutputLine(lines);
|
|
88
|
-
return lastLine ?
|
|
71
|
+
return lastLine ? clipText(lastLine, statusWidth) : '';
|
|
89
72
|
}
|
|
90
73
|
if (state === 'error') {
|
|
91
74
|
const errorCount = getErrorCount(lines);
|
|
92
|
-
|
|
75
|
+
const text = errorCount > 0 ? `${errorCount} error${errorCount > 1 ? 's' : ''}` : 'failed';
|
|
76
|
+
return clipText(text, statusWidth);
|
|
93
77
|
}
|
|
94
78
|
return ''; // success - no status text
|
|
95
79
|
}, [
|
|
@@ -119,6 +103,7 @@ export default /*#__PURE__*/ memo(function CompactProcessLine({ item, isSelected
|
|
|
119
103
|
// Status text color
|
|
120
104
|
const statusColor = state === 'error' ? 'red' : 'gray';
|
|
121
105
|
return /*#__PURE__*/ _jsxs(Box, {
|
|
106
|
+
width: terminalWidth,
|
|
122
107
|
children: [
|
|
123
108
|
/*#__PURE__*/ _jsx(Text, {
|
|
124
109
|
color: isSelected ? 'cyan' : undefined,
|
|
@@ -128,16 +113,22 @@ export default /*#__PURE__*/ memo(function CompactProcessLine({ item, isSelected
|
|
|
128
113
|
width: iconWidth,
|
|
129
114
|
children: icon
|
|
130
115
|
}),
|
|
131
|
-
/*#__PURE__*/ _jsx(
|
|
132
|
-
|
|
133
|
-
children:
|
|
116
|
+
/*#__PURE__*/ _jsx(Box, {
|
|
117
|
+
width: nameColumnWidth,
|
|
118
|
+
children: /*#__PURE__*/ _jsx(Text, {
|
|
119
|
+
inverse: isSelected,
|
|
120
|
+
children: clippedName
|
|
121
|
+
})
|
|
134
122
|
}),
|
|
135
|
-
statusText && /*#__PURE__*/
|
|
136
|
-
|
|
137
|
-
children:
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
123
|
+
statusWidth > 0 && statusText && /*#__PURE__*/ _jsx(Box, {
|
|
124
|
+
width: statusWidth + gap,
|
|
125
|
+
children: /*#__PURE__*/ _jsxs(Text, {
|
|
126
|
+
color: statusColor,
|
|
127
|
+
children: [
|
|
128
|
+
" ",
|
|
129
|
+
statusText
|
|
130
|
+
]
|
|
131
|
+
})
|
|
141
132
|
})
|
|
142
133
|
]
|
|
143
134
|
});
|
|
@@ -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 { useStore } from '../state/StoreContext.ts';\nimport type { ChildProcess, Line } from '../types.ts';\nimport { LineType } from '../types.ts';\nimport Spinner from './Spinner.ts';\n\
|
|
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 { SPINNER } from '../constants.ts';\nimport { clipText } from '../lib/clipText.ts';\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\ntype Props = {\n item: ChildProcess;\n isSelected?: boolean;\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 selectionWidth = 1; // selection indicator\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 = Math.max(0, terminalWidth - selectionWidth - iconWidth - nameColumnWidth - gap);\n\n // Clip name to column width and pad\n const clippedName = clipText(displayName, nameColumnWidth).padEnd(nameColumnWidth);\n\n // Status text based on state - clip to available width\n const statusText = useMemo(() => {\n if (state === 'running') {\n const lastLine = getLastOutputLine(lines);\n return lastLine ? clipText(lastLine, statusWidth) : '';\n }\n if (state === 'error') {\n const errorCount = getErrorCount(lines);\n const text = errorCount > 0 ? `${errorCount} error${errorCount > 1 ? 's' : ''}` : 'failed';\n return clipText(text, statusWidth);\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 width={terminalWidth}>\n <Text color={isSelected ? 'cyan' : undefined}>{selectionIndicator}</Text>\n <Box width={iconWidth}>{icon}</Box>\n <Box width={nameColumnWidth}>\n <Text inverse={isSelected}>{clippedName}</Text>\n </Box>\n {statusWidth > 0 && statusText && (\n <Box width={statusWidth + gap}>\n <Text color={statusColor}> {statusText}</Text>\n </Box>\n )}\n </Box>\n );\n});\n"],"names":["Box","Text","useStdout","memo","useMemo","SPINNER","clipText","figures","calculateColumnWidth","useStore","LineType","Spinner","getLastOutputLine","lines","i","length","text","getErrorCount","filter","line","type","stderr","CompactProcessLine","item","isSelected","store","stdout","terminalWidth","columns","group","title","state","selectionIndicator","pointer","displayName","selectionWidth","iconWidth","maxGroupLength","getMaxGroupLength","nameColumnWidth","gap","statusWidth","Math","max","clippedName","padEnd","statusText","lastLine","errorCount","icon","color","tick","cross","statusColor","width","undefined","inverse"],"mappings":"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"}
|
|
@@ -29,24 +29,9 @@ function _object_spread(target) {
|
|
|
29
29
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
30
30
|
import { Box, Text } from 'ink';
|
|
31
31
|
import { memo } from 'react';
|
|
32
|
+
import { SPINNER } from '../constants.js';
|
|
32
33
|
import figures from '../lib/figures.js';
|
|
33
34
|
import Spinner from './Spinner.js';
|
|
34
|
-
// From: https://github.com/sindresorhus/cli-spinners/blob/00de8fbeee16fa49502fa4f687449f70f2c8ca2c/spinners.json#L2
|
|
35
|
-
const SPINNER = {
|
|
36
|
-
interval: 80,
|
|
37
|
-
frames: [
|
|
38
|
-
'⠋',
|
|
39
|
-
'⠙',
|
|
40
|
-
'⠹',
|
|
41
|
-
'⠸',
|
|
42
|
-
'⠼',
|
|
43
|
-
'⠴',
|
|
44
|
-
'⠦',
|
|
45
|
-
'⠧',
|
|
46
|
-
'⠇',
|
|
47
|
-
'⠏'
|
|
48
|
-
]
|
|
49
|
-
};
|
|
50
35
|
export default /*#__PURE__*/ memo(function StatusBar({ running, done, errors, errorLines }) {
|
|
51
36
|
return /*#__PURE__*/ _jsxs(Box, {
|
|
52
37
|
justifyContent: "space-between",
|
|
@@ -58,12 +43,12 @@ export default /*#__PURE__*/ memo(function StatusBar({ running, done, errors, er
|
|
|
58
43
|
color: "green",
|
|
59
44
|
children: figures.tick
|
|
60
45
|
}),
|
|
61
|
-
` Running: ${running} `,
|
|
46
|
+
` Running: ${running} | `,
|
|
62
47
|
/*#__PURE__*/ _jsx(Text, {
|
|
63
48
|
color: "green",
|
|
64
49
|
children: figures.tick
|
|
65
50
|
}),
|
|
66
|
-
` Done: ${done} `,
|
|
51
|
+
` Done: ${done} | `,
|
|
67
52
|
/*#__PURE__*/ _jsx(Text, {
|
|
68
53
|
color: "red",
|
|
69
54
|
children: figures.cross
|
|
@@ -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 figures from '../lib/figures.ts';\nimport Spinner from './Spinner.ts';\n\
|
|
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 justifyContent=\"space-between\">\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 {errors > 0 && (\n <Box>\n <Text dimColor>[e]rrors</Text>\n </Box>\n )}\n </Box>\n );\n});\n"],"names":["Box","Text","memo","SPINNER","figures","Spinner","StatusBar","running","done","errors","errorLines","justifyContent","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,MAACV;QAAIW,gBAAe;;0BAClB,KAACX;0BACC,cAAA,MAACC;;wBACEM,UAAU,kBAAI,KAACF,4BAAYF,0BAAc,KAACF;4BAAKW,OAAM;sCAASR,QAAQS,IAAI;;wBAC1E,CAAC,UAAU,EAAEN,QAAQ,IAAI,CAAC;sCAC3B,KAACN;4BAAKW,OAAM;sCAASR,QAAQS,IAAI;;wBAChC,CAAC,OAAO,EAAEL,KAAK,IAAI,CAAC;sCACrB,KAACP;4BAAKW,OAAM;sCAAOR,QAAQU,KAAK;;wBAC/B,CAAC,SAAS,EAAEL,QAAQ;wBACpBC,aAAa,mBAAK,KAACT;4BAAKc,QAAQ;sCAAE,CAAC,EAAE,EAAEL,WAAW,OAAO,CAAC;;;;;YAG9DD,SAAS,mBACR,KAACT;0BACC,cAAA,KAACC;oBAAKc,QAAQ;8BAAC;;;;;AAKzB,GAAG"}
|
package/dist/esm/constants.js
CHANGED
|
@@ -9,3 +9,19 @@ export const BATCH_MAX_WAIT_MS = 50;
|
|
|
9
9
|
export const DEFAULT_MAX_FPS = 20;
|
|
10
10
|
// Expansion
|
|
11
11
|
export const EXPANDED_MAX_VISIBLE_LINES = 10;
|
|
12
|
+
// From: https://github.com/sindresorhus/cli-spinners/blob/00de8fbeee16fa49502fa4f687449f70f2c8ca2c/spinners.json#L2
|
|
13
|
+
export const SPINNER = {
|
|
14
|
+
interval: 80,
|
|
15
|
+
frames: [
|
|
16
|
+
'⠋',
|
|
17
|
+
'⠙',
|
|
18
|
+
'⠹',
|
|
19
|
+
'⠸',
|
|
20
|
+
'⠼',
|
|
21
|
+
'⠴',
|
|
22
|
+
'⠦',
|
|
23
|
+
'⠧',
|
|
24
|
+
'⠇',
|
|
25
|
+
'⠏'
|
|
26
|
+
]
|
|
27
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/constants.ts"],"sourcesContent":["// Column width defaults\nexport const DEFAULT_COLUMN_WIDTH = 15;\nexport const MAX_COLUMN_WIDTH_PERCENT = 0.4; // 40% of terminal width\nexport const FALLBACK_COLUMN_WIDTH = 25;\n\n// Batching defaults\nexport const BATCH_MAX_LINES = 20;\nexport const BATCH_MAX_WAIT_MS = 50;\n\n// Rendering\nexport const DEFAULT_MAX_FPS = 20;\n\n// Expansion\nexport const EXPANDED_MAX_VISIBLE_LINES = 10;\n"],"names":["DEFAULT_COLUMN_WIDTH","MAX_COLUMN_WIDTH_PERCENT","FALLBACK_COLUMN_WIDTH","BATCH_MAX_LINES","BATCH_MAX_WAIT_MS","DEFAULT_MAX_FPS","EXPANDED_MAX_VISIBLE_LINES"],"mappings":"AAAA,wBAAwB;AACxB,OAAO,MAAMA,uBAAuB,GAAG;AACvC,OAAO,MAAMC,2BAA2B,IAAI,CAAC,wBAAwB;AACrE,OAAO,MAAMC,wBAAwB,GAAG;AAExC,oBAAoB;AACpB,OAAO,MAAMC,kBAAkB,GAAG;AAClC,OAAO,MAAMC,oBAAoB,GAAG;AAEpC,YAAY;AACZ,OAAO,MAAMC,kBAAkB,GAAG;AAElC,YAAY;AACZ,OAAO,MAAMC,6BAA6B,GAAG"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/constants.ts"],"sourcesContent":["// Column width defaults\nexport const DEFAULT_COLUMN_WIDTH = 15;\nexport const MAX_COLUMN_WIDTH_PERCENT = 0.4; // 40% of terminal width\nexport const FALLBACK_COLUMN_WIDTH = 25;\n\n// Batching defaults\nexport const BATCH_MAX_LINES = 20;\nexport const BATCH_MAX_WAIT_MS = 50;\n\n// Rendering\nexport const DEFAULT_MAX_FPS = 20;\n\n// Expansion\nexport const EXPANDED_MAX_VISIBLE_LINES = 10;\n\n// From: https://github.com/sindresorhus/cli-spinners/blob/00de8fbeee16fa49502fa4f687449f70f2c8ca2c/spinners.json#L2\nexport const SPINNER = {\n interval: 80,\n frames: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'],\n};\n"],"names":["DEFAULT_COLUMN_WIDTH","MAX_COLUMN_WIDTH_PERCENT","FALLBACK_COLUMN_WIDTH","BATCH_MAX_LINES","BATCH_MAX_WAIT_MS","DEFAULT_MAX_FPS","EXPANDED_MAX_VISIBLE_LINES","SPINNER","interval","frames"],"mappings":"AAAA,wBAAwB;AACxB,OAAO,MAAMA,uBAAuB,GAAG;AACvC,OAAO,MAAMC,2BAA2B,IAAI,CAAC,wBAAwB;AACrE,OAAO,MAAMC,wBAAwB,GAAG;AAExC,oBAAoB;AACpB,OAAO,MAAMC,kBAAkB,GAAG;AAClC,OAAO,MAAMC,oBAAoB,GAAG;AAEpC,YAAY;AACZ,OAAO,MAAMC,kBAAkB,GAAG;AAElC,YAAY;AACZ,OAAO,MAAMC,6BAA6B,GAAG;AAE7C,oHAAoH;AACpH,OAAO,MAAMC,UAAU;IACrBC,UAAU;IACVC,QAAQ;QAAC;QAAK;QAAK;QAAK;QAAK;QAAK;QAAK;QAAK;QAAK;QAAK;KAAI;AAC5D,EAAE"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export function createSession(options) {
|
|
2
|
+
let realSession = null;
|
|
3
|
+
let loadError = null;
|
|
4
|
+
// Start loading immediately
|
|
5
|
+
import('./session.js').then((mod)=>{
|
|
6
|
+
realSession = mod.createSession(options);
|
|
7
|
+
}).catch((err)=>{
|
|
8
|
+
loadError = err;
|
|
9
|
+
});
|
|
10
|
+
return {
|
|
11
|
+
spawn (command, args, spawnOptions, processOptions, callback) {
|
|
12
|
+
if (loadError) {
|
|
13
|
+
callback(loadError);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
if (realSession) {
|
|
17
|
+
realSession.spawn(command, args, spawnOptions, processOptions, callback);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
// Still loading, wait for it
|
|
21
|
+
import('./session.js').then((mod)=>{
|
|
22
|
+
if (!realSession) realSession = mod.createSession(options);
|
|
23
|
+
realSession.spawn(command, args, spawnOptions, processOptions, callback);
|
|
24
|
+
}).catch(callback);
|
|
25
|
+
},
|
|
26
|
+
close () {
|
|
27
|
+
if (realSession) realSession.close();
|
|
28
|
+
},
|
|
29
|
+
waitAndClose (callback) {
|
|
30
|
+
if (realSession) {
|
|
31
|
+
realSession.waitAndClose(callback);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
// Still loading, wait for it
|
|
35
|
+
import('./session.js').then((mod)=>{
|
|
36
|
+
if (!realSession) realSession = mod.createSession(options);
|
|
37
|
+
realSession.waitAndClose(callback);
|
|
38
|
+
}).catch(()=>{
|
|
39
|
+
callback === null || callback === void 0 ? void 0 : callback();
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/createSessionWrapper.ts"],"sourcesContent":["import type { ProcessOptions, SessionOptions, SpawnError, SpawnOptions, TerminalCallback } 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\nexport function createSession(options?: SessionOptions): Session {\n let realSession: Session | null = null;\n let loadError: SpawnError | null = null;\n\n // Start loading immediately\n import('./session.ts')\n .then((mod) => {\n realSession = mod.createSession(options);\n })\n .catch((err) => {\n loadError = err;\n });\n\n return {\n spawn(command: string, args: string[], spawnOptions: SpawnOptions, processOptions: ProcessOptions, callback: TerminalCallback): void {\n if (loadError) {\n callback(loadError);\n return;\n }\n if (realSession) {\n realSession.spawn(command, args, spawnOptions, processOptions, callback);\n return;\n }\n // Still loading, wait for it\n import('./session.ts')\n .then((mod) => {\n if (!realSession) realSession = mod.createSession(options);\n realSession.spawn(command, args, spawnOptions, processOptions, callback);\n })\n .catch(callback);\n },\n close(): void {\n if (realSession) realSession.close();\n },\n waitAndClose(callback?: () => void): void {\n if (realSession) {\n realSession.waitAndClose(callback);\n return;\n }\n // Still loading, wait for it\n import('./session.ts')\n .then((mod) => {\n if (!realSession) realSession = mod.createSession(options);\n realSession.waitAndClose(callback);\n })\n .catch(() => {\n callback?.();\n });\n },\n };\n}\n"],"names":["createSession","options","realSession","loadError","then","mod","catch","err","spawn","command","args","spawnOptions","processOptions","callback","close","waitAndClose"],"mappings":"AAQA,OAAO,SAASA,cAAcC,OAAwB;IACpD,IAAIC,cAA8B;IAClC,IAAIC,YAA+B;IAEnC,4BAA4B;IAC5B,MAAM,CAAC,gBACJC,IAAI,CAAC,CAACC;QACLH,cAAcG,IAAIL,aAAa,CAACC;IAClC,GACCK,KAAK,CAAC,CAACC;QACNJ,YAAYI;IACd;IAEF,OAAO;QACLC,OAAMC,OAAe,EAAEC,IAAc,EAAEC,YAA0B,EAAEC,cAA8B,EAAEC,QAA0B;YAC3H,IAAIV,WAAW;gBACbU,SAASV;gBACT;YACF;YACA,IAAID,aAAa;gBACfA,YAAYM,KAAK,CAACC,SAASC,MAAMC,cAAcC,gBAAgBC;gBAC/D;YACF;YACA,6BAA6B;YAC7B,MAAM,CAAC,gBACJT,IAAI,CAAC,CAACC;gBACL,IAAI,CAACH,aAAaA,cAAcG,IAAIL,aAAa,CAACC;gBAClDC,YAAYM,KAAK,CAACC,SAASC,MAAMC,cAAcC,gBAAgBC;YACjE,GACCP,KAAK,CAACO;QACX;QACAC;YACE,IAAIZ,aAAaA,YAAYY,KAAK;QACpC;QACAC,cAAaF,QAAqB;YAChC,IAAIX,aAAa;gBACfA,YAAYa,YAAY,CAACF;gBACzB;YACF;YACA,6BAA6B;YAC7B,MAAM,CAAC,gBACJT,IAAI,CAAC,CAACC;gBACL,IAAI,CAACH,aAAaA,cAAcG,IAAIL,aAAa,CAACC;gBAClDC,YAAYa,YAAY,CAACF;YAC3B,GACCP,KAAK,CAAC;gBACLO,qBAAAA,+BAAAA;YACF;QACJ;IACF;AACF"}
|
package/dist/esm/index-esm.js
CHANGED
|
@@ -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 { createSession as createSessionImpl } from './
|
|
5
|
+
import { createSession as createSessionImpl } from './createSessionWrapper.js';
|
|
6
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 { createSession as createSessionImpl
|
|
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\nimport type { createSession as createSessionType, Session } from './createSessionWrapper.ts';\nexport type { Session };\n\nconst major = +process.versions.node.split('.')[0];\n\nimport { createSession as createSessionImpl } from './createSessionWrapper.ts';\nexport const createSession = major > 18 ? createSessionImpl : (undefined as typeof createSessionType);\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;AAK3B,MAAMC,QAAQ,CAACC,QAAQC,QAAQ,CAACC,IAAI,CAACC,KAAK,CAAC,IAAI,CAAC,EAAE;AAElD,SAASC,iBAAiBC,iBAAiB,QAAQ,4BAA4B;AAC/E,OAAO,MAAMD,gBAAgBL,QAAQ,KAAKM,oBAAqBC,UAAuC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import ansiRegex from './ansiRegex.js';
|
|
2
|
+
const regex = ansiRegex();
|
|
3
|
+
/**
|
|
4
|
+
* Get the visible length of a string, ignoring ANSI escape codes.
|
|
5
|
+
*/ export function visibleLength(str) {
|
|
6
|
+
return str.replace(regex, '').length;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Clip text to a maximum visible width, accounting for ANSI escape codes.
|
|
10
|
+
* Adds ellipsis (…) if truncated.
|
|
11
|
+
*/ export function clipText(str, maxWidth) {
|
|
12
|
+
if (maxWidth <= 0) return '';
|
|
13
|
+
if (maxWidth === 1) return '…';
|
|
14
|
+
const stripped = str.replace(regex, '');
|
|
15
|
+
if (stripped.length <= maxWidth) return str;
|
|
16
|
+
// Need to truncate - walk through and preserve ANSI codes
|
|
17
|
+
let visibleCount = 0;
|
|
18
|
+
let result = '';
|
|
19
|
+
let i = 0;
|
|
20
|
+
while(i < str.length && visibleCount < maxWidth - 1){
|
|
21
|
+
// Check for ANSI escape sequence (ESC [ params m)
|
|
22
|
+
const remaining = str.slice(i);
|
|
23
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI escape requires control char
|
|
24
|
+
const match = remaining.match(/^(\u001B\[[0-9;]*m)/);
|
|
25
|
+
if (match) {
|
|
26
|
+
// Include the ANSI code in output but don't count toward visible length
|
|
27
|
+
result += match[1];
|
|
28
|
+
i += match[1].length;
|
|
29
|
+
} else {
|
|
30
|
+
// Regular character
|
|
31
|
+
result += str[i];
|
|
32
|
+
visibleCount++;
|
|
33
|
+
i++;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return `${result}…`;
|
|
37
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/node/spawn-term/src/lib/clipText.ts"],"sourcesContent":["import ansiRegex from './ansiRegex.ts';\n\nconst regex = ansiRegex();\n\n/**\n * Get the visible length of a string, ignoring ANSI escape codes.\n */\nexport function visibleLength(str: string): number {\n return str.replace(regex, '').length;\n}\n\n/**\n * Clip text to a maximum visible width, accounting for ANSI escape codes.\n * Adds ellipsis (…) if truncated.\n */\nexport function clipText(str: string, maxWidth: number): string {\n if (maxWidth <= 0) return '';\n if (maxWidth === 1) return '…';\n\n const stripped = str.replace(regex, '');\n if (stripped.length <= maxWidth) return str;\n\n // Need to truncate - walk through and preserve ANSI codes\n let visibleCount = 0;\n let result = '';\n let i = 0;\n\n while (i < str.length && visibleCount < maxWidth - 1) {\n // Check for ANSI escape sequence (ESC [ params m)\n const remaining = str.slice(i);\n // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI escape requires control char\n const match = remaining.match(/^(\\u001B\\[[0-9;]*m)/);\n\n if (match) {\n // Include the ANSI code in output but don't count toward visible length\n result += match[1];\n i += match[1].length;\n } else {\n // Regular character\n result += str[i];\n visibleCount++;\n i++;\n }\n }\n\n return `${result}…`;\n}\n"],"names":["ansiRegex","regex","visibleLength","str","replace","length","clipText","maxWidth","stripped","visibleCount","result","i","remaining","slice","match"],"mappings":"AAAA,OAAOA,eAAe,iBAAiB;AAEvC,MAAMC,QAAQD;AAEd;;CAEC,GACD,OAAO,SAASE,cAAcC,GAAW;IACvC,OAAOA,IAAIC,OAAO,CAACH,OAAO,IAAII,MAAM;AACtC;AAEA;;;CAGC,GACD,OAAO,SAASC,SAASH,GAAW,EAAEI,QAAgB;IACpD,IAAIA,YAAY,GAAG,OAAO;IAC1B,IAAIA,aAAa,GAAG,OAAO;IAE3B,MAAMC,WAAWL,IAAIC,OAAO,CAACH,OAAO;IACpC,IAAIO,SAASH,MAAM,IAAIE,UAAU,OAAOJ;IAExC,0DAA0D;IAC1D,IAAIM,eAAe;IACnB,IAAIC,SAAS;IACb,IAAIC,IAAI;IAER,MAAOA,IAAIR,IAAIE,MAAM,IAAII,eAAeF,WAAW,EAAG;QACpD,kDAAkD;QAClD,MAAMK,YAAYT,IAAIU,KAAK,CAACF;QAC5B,6FAA6F;QAC7F,MAAMG,QAAQF,UAAUE,KAAK,CAAC;QAE9B,IAAIA,OAAO;YACT,wEAAwE;YACxEJ,UAAUI,KAAK,CAAC,EAAE;YAClBH,KAAKG,KAAK,CAAC,EAAE,CAACT,MAAM;QACtB,OAAO;YACL,oBAAoB;YACpBK,UAAUP,GAAG,CAACQ,EAAE;YAChBF;YACAE;QACF;IACF;IAEA,OAAO,GAAGD,OAAO,CAAC,CAAC;AACrB"}
|
package/dist/esm/session.js
CHANGED
|
@@ -166,14 +166,34 @@ class SessionImpl {
|
|
|
166
166
|
}
|
|
167
167
|
if (callback) this.waitCallbacks.push(callback);
|
|
168
168
|
if (this.runningCount === 0) {
|
|
169
|
-
this.
|
|
169
|
+
if (this.isInteractive) {
|
|
170
|
+
// In interactive mode, wait for user to quit (press 'q')
|
|
171
|
+
const unsubscribe = this.store.subscribe(()=>{
|
|
172
|
+
if (this.store.getShouldExit()) {
|
|
173
|
+
unsubscribe();
|
|
174
|
+
this.closeAndCallWaitCallbacks();
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
} else {
|
|
178
|
+
this.closeAndCallWaitCallbacks();
|
|
179
|
+
}
|
|
170
180
|
}
|
|
171
181
|
// If runningCount > 0, will close when it hits 0 in onProcessComplete
|
|
172
182
|
}
|
|
173
183
|
onProcessComplete() {
|
|
174
184
|
this.runningCount--;
|
|
175
185
|
if (this.runningCount === 0 && this.waitCallbacks.length > 0) {
|
|
176
|
-
this.
|
|
186
|
+
if (this.isInteractive) {
|
|
187
|
+
// In interactive mode, wait for user to quit (press 'q')
|
|
188
|
+
const unsubscribe = this.store.subscribe(()=>{
|
|
189
|
+
if (this.store.getShouldExit()) {
|
|
190
|
+
unsubscribe();
|
|
191
|
+
this.closeAndCallWaitCallbacks();
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
} else {
|
|
195
|
+
this.closeAndCallWaitCallbacks();
|
|
196
|
+
}
|
|
177
197
|
}
|
|
178
198
|
}
|
|
179
199
|
closeAndCallWaitCallbacks() {
|
|
@@ -212,6 +232,8 @@ class SessionImpl {
|
|
|
212
232
|
this.closed = false;
|
|
213
233
|
this.waitCallbacks = [];
|
|
214
234
|
this.store = new ProcessStore(options);
|
|
235
|
+
var _options_interactive;
|
|
236
|
+
this.isInteractive = (_options_interactive = options.interactive) !== null && _options_interactive !== void 0 ? _options_interactive : false;
|
|
215
237
|
// Render Ink app immediately
|
|
216
238
|
this.inkApp = render(/*#__PURE__*/ _jsx(App, {
|
|
217
239
|
store: this.store
|
package/dist/esm/session.js.map
CHANGED
|
@@ -1 +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 (callback) this.waitCallbacks.push(callback);\n\n if (this.runningCount === 0) {\n this.closeAndCallWaitCallbacks();\n }\n // If runningCount > 0, will close when it hits 0 in onProcessComplete\n }\n\n private onProcessComplete(): void {\n this.runningCount--;\n if (this.runningCount === 0 && this.waitCallbacks.length > 0) {\n this.closeAndCallWaitCallbacks();\n }\n }\n\n private closeAndCallWaitCallbacks(): void {\n if (this.closed) return;\n this.closed = true;\n this.cleanup(() => {\n for (const cb of this.waitCallbacks) cb();\n this.waitCallbacks = [];\n });\n }\n\n private cleanup(onComplete?: () => void): 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 onComplete?.();\n })\n .catch(() => {\n const cb = this.store.getExitCallback();\n cb?.();\n onComplete?.();\n });\n this.inkApp = null;\n } else {\n onComplete?.();\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","closeAndCallWaitCallbacks","length","cb","onComplete","signalExit","reset","process","write","inkApp","waitUntilExit","then","getExitCallback","catch","incrementalRendering","maxFps","createSession"],"mappings":"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"}
|
|
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 private isInteractive: boolean;\n\n constructor(options: SessionOptions = {}) {\n this.store = new ProcessStore(options);\n this.isInteractive = options.interactive ?? false;\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 (callback) this.waitCallbacks.push(callback);\n\n if (this.runningCount === 0) {\n if (this.isInteractive) {\n // In interactive mode, wait for user to quit (press 'q')\n const unsubscribe = this.store.subscribe(() => {\n if (this.store.getShouldExit()) {\n unsubscribe();\n this.closeAndCallWaitCallbacks();\n }\n });\n } else {\n this.closeAndCallWaitCallbacks();\n }\n }\n // If runningCount > 0, will close when it hits 0 in onProcessComplete\n }\n\n private onProcessComplete(): void {\n this.runningCount--;\n if (this.runningCount === 0 && this.waitCallbacks.length > 0) {\n if (this.isInteractive) {\n // In interactive mode, wait for user to quit (press 'q')\n const unsubscribe = this.store.subscribe(() => {\n if (this.store.getShouldExit()) {\n unsubscribe();\n this.closeAndCallWaitCallbacks();\n }\n });\n } else {\n this.closeAndCallWaitCallbacks();\n }\n }\n }\n\n private closeAndCallWaitCallbacks(): void {\n if (this.closed) return;\n this.closed = true;\n this.cleanup(() => {\n for (const cb of this.waitCallbacks) cb();\n this.waitCallbacks = [];\n });\n }\n\n private cleanup(onComplete?: () => void): 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 onComplete?.();\n })\n .catch(() => {\n const cb = this.store.getExitCallback();\n cb?.();\n onComplete?.();\n });\n this.inkApp = null;\n } else {\n onComplete?.();\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","isInteractive","unsubscribe","subscribe","getShouldExit","closeAndCallWaitCallbacks","length","cb","onComplete","signalExit","reset","process","write","inkApp","waitUntilExit","then","getExitCallback","catch","interactive","incrementalRendering","maxFps","createSession"],"mappings":"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"}
|
|
@@ -5,3 +5,7 @@ export declare const BATCH_MAX_LINES = 20;
|
|
|
5
5
|
export declare const BATCH_MAX_WAIT_MS = 50;
|
|
6
6
|
export declare const DEFAULT_MAX_FPS = 20;
|
|
7
7
|
export declare const EXPANDED_MAX_VISIBLE_LINES = 10;
|
|
8
|
+
export declare const SPINNER: {
|
|
9
|
+
interval: number;
|
|
10
|
+
frames: string[];
|
|
11
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ProcessOptions, SessionOptions, SpawnOptions, TerminalCallback } from './types.js';
|
|
2
|
+
export interface Session {
|
|
3
|
+
spawn(command: string, args: string[], spawnOptions: SpawnOptions, options: ProcessOptions, callback: TerminalCallback): void;
|
|
4
|
+
close(): void;
|
|
5
|
+
waitAndClose(callback?: () => void): void;
|
|
6
|
+
}
|
|
7
|
+
export declare function createSession(options?: SessionOptions): Session;
|
|
@@ -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 { createSession as
|
|
4
|
+
import type { createSession as createSessionType, Session } from './createSessionWrapper.js';
|
|
5
5
|
export type { Session };
|
|
6
|
-
export declare const createSession: typeof
|
|
6
|
+
export declare const createSession: typeof createSessionType;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get the visible length of a string, ignoring ANSI escape codes.
|
|
3
|
+
*/
|
|
4
|
+
export declare function visibleLength(str: string): number;
|
|
5
|
+
/**
|
|
6
|
+
* Clip text to a maximum visible width, accounting for ANSI escape codes.
|
|
7
|
+
* Adds ellipsis (…) if truncated.
|
|
8
|
+
*/
|
|
9
|
+
export declare function clipText(str: string, maxWidth: number): string;
|
|
@@ -12,6 +12,7 @@ export declare class ProcessStore {
|
|
|
12
12
|
private selectedErrorIndex;
|
|
13
13
|
private expandedId;
|
|
14
14
|
private scrollOffset;
|
|
15
|
+
private listScrollOffset;
|
|
15
16
|
private header;
|
|
16
17
|
private showStatusBar;
|
|
17
18
|
private isInteractive;
|
|
@@ -31,6 +32,7 @@ export declare class ProcessStore {
|
|
|
31
32
|
getSelectedErrorIndex: () => number;
|
|
32
33
|
getExpandedId: () => string | null;
|
|
33
34
|
getScrollOffset: () => number;
|
|
35
|
+
getListScrollOffset: () => number;
|
|
34
36
|
getHeader: () => string | undefined;
|
|
35
37
|
getShowStatusBar: () => boolean;
|
|
36
38
|
getIsInteractive: () => boolean;
|
|
@@ -40,8 +42,9 @@ export declare class ProcessStore {
|
|
|
40
42
|
appendLines(id: string, newLines: Line[]): void;
|
|
41
43
|
getProcess(id: string): ChildProcess | undefined;
|
|
42
44
|
setMode(mode: Mode): void;
|
|
43
|
-
selectNext(): void;
|
|
44
|
-
selectPrev(): void;
|
|
45
|
+
selectNext(visibleCount?: number): void;
|
|
46
|
+
selectPrev(visibleCount?: number): void;
|
|
47
|
+
private adjustListScroll;
|
|
45
48
|
getSelectedProcess(): ChildProcess | undefined;
|
|
46
49
|
selectNextError(): void;
|
|
47
50
|
selectPrevError(): void;
|