11const childProcess = require ( 'child_process' ) ;
2+ const net = require ( 'net' ) ;
3+ const stream = require ( 'stream' ) ;
4+ const TEN_MEBIBYTE = 1024 * 1024 * 10 ;
25
3- const TEN_MEBIBYTE = 1024 * 1024 * 10
46
57async function exec ( command , args , stdin , cwd = './' , detached , env ) {
68 let stdout = '' ;
79 let stderr = '' ;
810
911 const spawnOptions = { maxBuffer : TEN_MEBIBYTE , cwd, detached, env } ;
1012
11- const child = childProcess . spawn ( command , args , spawnOptions ) ;
13+ const child = childExec ( command , args , stdin , spawnOptions ) ;
1214
1315 // Capture stdout
1416 child . stdout . on ( 'data' , data => {
@@ -20,13 +22,6 @@ async function exec(command, args, stdin, cwd = './', detached, env) {
2022 stderr += data ;
2123 } ) ;
2224
23- // Write to stdin if provided
24- if ( stdin ) {
25- child . stdin . setEncoding ( 'utf-8' ) ;
26- child . stdin . write ( stdin ) ;
27- child . stdin . end ( ) ;
28- }
29-
3025 // Wait for the child process to exit
3126 await new Promise ( ( resolve , reject ) => {
3227 child . on ( 'close' , code => {
@@ -49,38 +44,116 @@ async function exec(command, args, stdin, cwd = './', detached, env) {
4944async function streamExec ( command , args , stdin , cwd = './' , detached , env ) {
5045 const spawnOptions = { maxBuffer : TEN_MEBIBYTE , cwd, detached, env } ;
5146
52- const child = childProcess . spawn ( command , args , spawnOptions ) ;
47+ const child = childExec ( command , args , stdin , spawnOptions ) ;
48+
49+ return {
50+ stdout : child . stdout ,
51+ stderr : child . stderr ,
52+ promise : new Promise ( ( resolve , reject ) => {
53+ child . on ( 'close' , code => {
54+ if ( code !== 0 ) {
55+ reject ( new Error ( `Child process exited with code ${ code } ` ) ) ;
56+ } else {
57+ resolve ( ) ;
58+ }
59+ } ) ;
60+
61+ child . on ( 'error' , error => {
62+ reject ( error ) ;
63+ } ) ;
64+ } )
65+ }
66+ }
67+
68+ async function streamExecWithEvents ( command , args , stdin , cwd = './' , env ) {
69+ let server , events = null ;
70+ const spawnOptions = { maxBuffer : TEN_MEBIBYTE , cwd, env, stdio : [ 'pipe' , 'pipe' , 'pipe' ] } ;
71+
72+ // On Windows, the child process doesn't know which file handles are available to it.
73+ // Therefore, we have to use a named pipe. This is set up with a server.
74+ if ( process . platform === 'win32' ) {
75+ const namedPipe = '\\\\.\\pipe\\gptscript-' + Math . floor ( Math . random ( ) * 1000000 ) ;
76+ events = new stream . Readable ( {
77+ encoding : 'utf-8' ,
78+ read ( ) {
79+ }
80+ } ) ;
81+
82+ server = net . createServer ( ( connection ) => {
83+ console . debug ( 'Client connected' ) ;
84+
85+ connection . on ( 'data' , ( data ) => {
86+ // Pass the data onto the event stream.
87+ events . push ( data ) ;
88+ } ) ;
89+
90+ connection . on ( 'end' , ( ) => {
91+ // Indicate that there is no more data.
92+ events . push ( null ) ;
93+ } ) ;
94+ } ) ;
95+
96+ server . listen ( namedPipe , ( ) => {
97+ console . debug ( 'Server is listening on' , namedPipe ) ;
98+ } ) ;
99+
100+ // Add the named pipe for streaming events.
101+ args . unshift ( "--events-stream-to=" + namedPipe ) ;
102+ } else {
103+ // For non-Windows systems, we just add an extra stdio pipe and use that for streaming events.
104+ spawnOptions . stdio . push ( 'pipe' ) ;
105+ args . unshift ( "--events-stream-to=fd://" + ( spawnOptions . stdio . length - 1 ) ) ;
106+ }
107+
108+
109+ const child = childExec ( command , args , stdin , spawnOptions ) ;
110+ if ( ! events ) {
111+ // If the child process is not a Windows system, we can use the stdio pipe for streaming events.
112+ events = stream . Readable . from ( child . stdio [ child . stdio . length - 1 ] )
113+ }
114+
115+ return {
116+ stdout : child . stdout ,
117+ stderr : child . stderr ,
118+ events : events ,
119+ promise : new Promise ( ( resolve , reject ) => {
120+ child . on ( 'exit' , code => {
121+ events . destroy ( ) ;
122+ if ( server ) server . close ( ) ;
123+
124+ if ( code !== 0 ) {
125+ reject ( new Error ( `Child process exited with code ${ code } ` ) ) ;
126+ } else {
127+ resolve ( ) ;
128+ }
129+ } ) ;
130+
131+ child . on ( 'error' , error => {
132+ events . destroy ( ) ;
133+ if ( server ) server . close ( ) ;
134+
135+ reject ( error ) ;
136+ } ) ;
137+ } )
138+ } ;
139+ }
140+
141+ function childExec ( command , args , stdin , opts = { } ) {
142+ const child = childProcess . spawn ( command , args , opts ) ;
53143
54144 // Write to stdin if provided
55145 if ( stdin && child . stdin ) {
56- child . stdin . setEncoding ( 'utf-8' ) ;
146+ child . stdin . setDefaultEncoding ( 'utf-8' ) ;
57147 child . stdin . write ( stdin ) ;
58148 child . stdin . end ( ) ;
59149 }
60150
61- if ( child . stdout && child . stdout ) {
62- return {
63- stdout : child . stdout ,
64- stderr : child . stderr ,
65- promise : new Promise ( ( resolve , reject ) => {
66- child . on ( 'close' , code => {
67- if ( code !== 0 ) {
68- reject ( new Error ( `Child process exited with code ${ code } ` ) ) ;
69- } else {
70- resolve ( ) ;
71- }
72- } ) ;
73-
74- child . on ( 'error' , error => {
75- reject ( error ) ;
76- } ) ;
77- } )
78- } ;
79- }
151+ return child ;
80152}
81153
82154
83155module . exports = {
84156 exec : exec ,
85- streamExec : streamExec
157+ streamExec : streamExec ,
158+ streamExecWithEvents : streamExecWithEvents
86159}
0 commit comments