@@ -117,6 +117,14 @@ export interface CommandValidation {
117117 warning ?: string
118118}
119119
120+ // Interface for timestamped output chunks
121+ interface TimestampedChunk {
122+ timestamp : number
123+ isStdout : boolean
124+ content : string
125+ isFirst : boolean
126+ }
127+
120128export class ExecuteBash {
121129 private readonly command : string
122130 private readonly workingDirectory ?: string
@@ -207,22 +215,68 @@ export class ExecuteBash {
207215 const stdoutBuffer : string [ ] = [ ]
208216 const stderrBuffer : string [ ] = [ ]
209217
210- let firstChunk = true
211- let firstStderrChunk = true
218+ // Use a closure boolean value firstChunk and a function to get and set its value
219+ let isFirstChunk = true
220+ const getAndSetFirstChunk = ( newValue : boolean ) : boolean => {
221+ const oldValue = isFirstChunk
222+ isFirstChunk = newValue
223+ return oldValue
224+ }
225+
226+ // Use a queue to maintain chronological order of chunks
227+ // This ensures that the output is processed in the exact order it was generated by the child process.
228+ const outputQueue : TimestampedChunk [ ] = [ ]
229+ let processingQueue = false
230+
231+ // Process the queue in order
232+ const processQueue = ( ) => {
233+ if ( processingQueue || outputQueue . length === 0 ) {
234+ return
235+ }
236+
237+ processingQueue = true
238+
239+ try {
240+ // Sort by timestamp to ensure chronological order
241+ outputQueue . sort ( ( a , b ) => a . timestamp - b . timestamp )
242+
243+ while ( outputQueue . length > 0 ) {
244+ const chunk = outputQueue . shift ( ) !
245+ ExecuteBash . handleTimestampedChunk ( chunk , stdoutBuffer , stderrBuffer , updates )
246+ }
247+ } finally {
248+ processingQueue = false
249+ }
250+ }
251+
212252 const childProcessOptions : ChildProcessOptions = {
213253 spawnOptions : {
214254 cwd : this . workingDirectory ,
215255 stdio : [ 'pipe' , 'pipe' , 'pipe' ] ,
216256 } ,
217257 collect : false ,
218258 waitForStreams : true ,
219- onStdout : ( chunk : string ) => {
220- ExecuteBash . handleChunk ( firstChunk ? '```console\n' + chunk : chunk , stdoutBuffer , updates )
221- firstChunk = false
259+ onStdout : async ( chunk : string ) => {
260+ const isFirst = getAndSetFirstChunk ( false )
261+ const timestamp = Date . now ( )
262+ outputQueue . push ( {
263+ timestamp,
264+ isStdout : true ,
265+ content : chunk ,
266+ isFirst,
267+ } )
268+ processQueue ( )
222269 } ,
223- onStderr : ( chunk : string ) => {
224- ExecuteBash . handleChunk ( firstStderrChunk ? '```console\n' + chunk : chunk , stderrBuffer , updates )
225- firstStderrChunk = false
270+ onStderr : async ( chunk : string ) => {
271+ const isFirst = getAndSetFirstChunk ( false )
272+ const timestamp = Date . now ( )
273+ outputQueue . push ( {
274+ timestamp,
275+ isStdout : false ,
276+ content : chunk ,
277+ isFirst,
278+ } )
279+ processQueue ( )
226280 } ,
227281 }
228282
@@ -261,6 +315,17 @@ export class ExecuteBash {
261315 } )
262316 }
263317
318+ private static handleTimestampedChunk (
319+ chunk : TimestampedChunk ,
320+ stdoutBuffer : string [ ] ,
321+ stderrBuffer : string [ ] ,
322+ updates ?: Writable
323+ ) : void {
324+ const buffer = chunk . isStdout ? stdoutBuffer : stderrBuffer
325+ const content = chunk . isFirst ? '```console\n' + chunk . content : chunk . content
326+ ExecuteBash . handleChunk ( content , buffer , updates )
327+ }
328+
264329 private static handleChunk ( chunk : string , buffer : string [ ] , updates ?: Writable ) {
265330 try {
266331 updates ?. write ( chunk )
0 commit comments