@@ -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,8 +215,33 @@ export class ExecuteBash {
207215 const stdoutBuffer : string [ ] = [ ]
208216 const stderrBuffer : string [ ] = [ ]
209217
210- let firstChunk = true
211- let firstStderrChunk = true
218+ // Use a queue to maintain chronological order of chunks
219+ // This ensures that the output is processed in the exact order it was generated by the child process.
220+ const outputQueue : TimestampedChunk [ ] = [ ]
221+ let processingQueue = false
222+ const firstChunk = new AtomicBoolean ( true )
223+
224+ // Process the queue in order
225+ const processQueue = ( ) => {
226+ if ( processingQueue || outputQueue . length === 0 ) {
227+ return
228+ }
229+
230+ processingQueue = true
231+
232+ try {
233+ // Sort by timestamp to ensure chronological order
234+ outputQueue . sort ( ( a , b ) => a . timestamp - b . timestamp )
235+
236+ while ( outputQueue . length > 0 ) {
237+ const chunk = outputQueue . shift ( ) !
238+ ExecuteBash . handleTimestampedChunk ( chunk , stdoutBuffer , stderrBuffer , updates )
239+ }
240+ } finally {
241+ processingQueue = false
242+ }
243+ }
244+
212245 const childProcessOptions : ChildProcessOptions = {
213246 spawnOptions : {
214247 cwd : this . workingDirectory ,
@@ -217,12 +250,26 @@ export class ExecuteBash {
217250 collect : false ,
218251 waitForStreams : true ,
219252 onStdout : ( chunk : string ) => {
220- ExecuteBash . handleChunk ( firstChunk ? '```console\n' + chunk : chunk , stdoutBuffer , updates )
221- firstChunk = false
253+ const isFirst = firstChunk . getAndSet ( false )
254+ const timestamp = Date . now ( )
255+ outputQueue . push ( {
256+ timestamp,
257+ isStdout : true ,
258+ content : chunk ,
259+ isFirst,
260+ } )
261+ processQueue ( )
222262 } ,
223263 onStderr : ( chunk : string ) => {
224- ExecuteBash . handleChunk ( firstStderrChunk ? '```console\n' + chunk : chunk , stderrBuffer , updates )
225- firstStderrChunk = false
264+ const isFirst = firstChunk . getAndSet ( false )
265+ const timestamp = Date . now ( )
266+ outputQueue . push ( {
267+ timestamp,
268+ isStdout : false ,
269+ content : chunk ,
270+ isFirst,
271+ } )
272+ processQueue ( )
226273 } ,
227274 }
228275
@@ -261,6 +308,17 @@ export class ExecuteBash {
261308 } )
262309 }
263310
311+ private static handleTimestampedChunk (
312+ chunk : TimestampedChunk ,
313+ stdoutBuffer : string [ ] ,
314+ stderrBuffer : string [ ] ,
315+ updates ?: Writable
316+ ) : void {
317+ const buffer = chunk . isStdout ? stdoutBuffer : stderrBuffer
318+ const content = chunk . isFirst ? '```console\n' + chunk . content : chunk . content
319+ ExecuteBash . handleChunk ( content , buffer , updates )
320+ }
321+
264322 private static handleChunk ( chunk : string , buffer : string [ ] , updates ?: Writable ) {
265323 try {
266324 updates ?. write ( chunk )
@@ -307,3 +365,17 @@ export class ExecuteBash {
307365 updates . end ( )
308366 }
309367}
368+
369+ class AtomicBoolean {
370+ private value : boolean
371+
372+ constructor ( initialValue : boolean ) {
373+ this . value = initialValue
374+ }
375+
376+ public getAndSet ( newValue : boolean ) : boolean {
377+ const oldValue = this . value
378+ this . value = newValue
379+ return oldValue
380+ }
381+ }
0 commit comments