@@ -369,9 +369,6 @@ export class CodeQLCliServer implements Disposable {
369369 onLine ?: OnLineCallback ,
370370 silent ?: boolean ,
371371 ) : Promise < string > {
372- const stderrBuffers : Buffer [ ] = [ ] ;
373- // The current buffer of stderr of a single line. To be used for logging.
374- let currentLineStderrBuffer : Buffer = Buffer . alloc ( 0 ) ;
375372 if ( this . commandInProcess ) {
376373 throw new Error ( "runCodeQlCliInternal called while cli was running" ) ;
377374 }
@@ -383,8 +380,6 @@ export class CodeQLCliServer implements Disposable {
383380 }
384381 // Grab the process so that typescript know that it is always defined.
385382 const process = this . process ;
386- // The array of fragments of stdout
387- const stdoutBuffers : Buffer [ ] = [ ] ;
388383
389384 // Compute the full args array
390385 const args = command . concat ( LOGGING_FLAGS ) . concat ( commandArgs ) ;
@@ -396,26 +391,83 @@ export class CodeQLCliServer implements Disposable {
396391 ) ;
397392 }
398393 try {
399- await new Promise < void > ( ( resolve , reject ) => {
400- // Start listening to stdout
401- process . stdout . addListener ( "data" , ( newData : Buffer ) => {
402- if ( onLine ) {
403- void ( async ( ) => {
404- const response = await onLine ( newData . toString ( "utf-8" ) ) ;
405-
406- if ( ! response ) {
407- return ;
408- }
409-
410- process . stdin . write ( `${ response } ${ EOL } ` ) ;
411-
412- // Remove newData from stdoutBuffers because the data has been consumed
413- // by the onLine callback.
414- stdoutBuffers . splice ( stdoutBuffers . indexOf ( newData ) , 1 ) ;
415- } ) ( ) ;
416- }
394+ return await this . handleProcessOutput ( process , {
395+ handleNullTerminator : true ,
396+ onListenStart : ( process ) => {
397+ // Write the command followed by a null terminator.
398+ process . stdin . write ( JSON . stringify ( args ) , "utf8" ) ;
399+ process . stdin . write ( this . nullBuffer ) ;
400+ } ,
401+ description,
402+ args,
403+ silent,
404+ onLine,
405+ } ) ;
406+ } catch ( err ) {
407+ // Kill the process if it isn't already dead.
408+ this . killProcessIfRunning ( ) ;
409+
410+ throw err ;
411+ }
412+ } finally {
413+ this . commandInProcess = false ;
414+ // start running the next command immediately
415+ this . runNext ( ) ;
416+ }
417+ }
418+
419+ private async handleProcessOutput (
420+ process : ChildProcessWithoutNullStreams ,
421+ {
422+ handleNullTerminator,
423+ args,
424+ description,
425+ onLine,
426+ onListenStart,
427+ silent,
428+ } : {
429+ handleNullTerminator : boolean ;
430+ args : string [ ] ;
431+ description : string ;
432+ onLine ?: OnLineCallback ;
433+ onListenStart ?: ( process : ChildProcessWithoutNullStreams ) => void ;
434+ silent ?: boolean ;
435+ } ,
436+ ) : Promise < string > {
437+ const stderrBuffers : Buffer [ ] = [ ] ;
438+ // The current buffer of stderr of a single line. To be used for logging.
439+ let currentLineStderrBuffer : Buffer = Buffer . alloc ( 0 ) ;
440+
441+ // The listeners of the process. Declared here so they can be removed in the finally block.
442+ let stdoutListener : ( ( newData : Buffer ) => void ) | undefined = undefined ;
443+ let stderrListener : ( ( newData : Buffer ) => void ) | undefined = undefined ;
444+ let closeListener : ( ( code : number | null ) => void ) | undefined = undefined ;
445+
446+ try {
447+ // The array of fragments of stdout
448+ const stdoutBuffers : Buffer [ ] = [ ] ;
417449
418- stdoutBuffers . push ( newData ) ;
450+ await new Promise < void > ( ( resolve , reject ) => {
451+ stdoutListener = ( newData : Buffer ) => {
452+ if ( onLine ) {
453+ void ( async ( ) => {
454+ const response = await onLine ( newData . toString ( "utf-8" ) ) ;
455+
456+ if ( ! response ) {
457+ return ;
458+ }
459+
460+ process . stdin . write ( `${ response } ${ EOL } ` ) ;
461+
462+ // Remove newData from stdoutBuffers because the data has been consumed
463+ // by the onLine callback.
464+ stdoutBuffers . splice ( stdoutBuffers . indexOf ( newData ) , 1 ) ;
465+ } ) ( ) ;
466+ }
467+
468+ stdoutBuffers . push ( newData ) ;
469+
470+ if ( handleNullTerminator ) {
419471 // If the buffer ends in '0' then exit.
420472 // We don't have to check the middle as no output will be written after the null until
421473 // the next command starts
@@ -425,89 +477,104 @@ export class CodeQLCliServer implements Disposable {
425477 ) {
426478 resolve ( ) ;
427479 }
428- } ) ;
429- // Listen to stderr
430- process . stderr . addListener ( "data" , ( newData : Buffer ) => {
431- stderrBuffers . push ( newData ) ;
432-
433- if ( ! silent ) {
434- currentLineStderrBuffer = Buffer . concat ( [
435- currentLineStderrBuffer ,
436- newData ,
437- ] ) ;
438-
439- // Print the stderr to the logger as it comes in. We need to ensure that
440- // we don't split messages on the same line, so we buffer the stderr and
441- // split it on EOLs.
442- const eolBuffer = Buffer . from ( EOL ) ;
443-
444- let hasCreatedSubarray = false ;
445-
446- let eolIndex ;
447- while (
448- ( eolIndex = currentLineStderrBuffer . indexOf ( eolBuffer ) ) !== - 1
449- ) {
450- const line = currentLineStderrBuffer . subarray ( 0 , eolIndex ) ;
451- void this . logger . log ( line . toString ( "utf-8" ) ) ;
452- currentLineStderrBuffer = currentLineStderrBuffer . subarray (
453- eolIndex + eolBuffer . length ,
454- ) ;
455- hasCreatedSubarray = true ;
456- }
480+ }
481+ } ;
482+ stderrListener = ( newData : Buffer ) => {
483+ stderrBuffers . push ( newData ) ;
484+
485+ if ( ! silent ) {
486+ currentLineStderrBuffer = Buffer . concat ( [
487+ currentLineStderrBuffer ,
488+ newData ,
489+ ] ) ;
490+
491+ // Print the stderr to the logger as it comes in. We need to ensure that
492+ // we don't split messages on the same line, so we buffer the stderr and
493+ // split it on EOLs.
494+ const eolBuffer = Buffer . from ( EOL ) ;
495+
496+ let hasCreatedSubarray = false ;
497+
498+ let eolIndex ;
499+ while (
500+ ( eolIndex = currentLineStderrBuffer . indexOf ( eolBuffer ) ) !== - 1
501+ ) {
502+ const line = currentLineStderrBuffer . subarray ( 0 , eolIndex ) ;
503+ void this . logger . log ( line . toString ( "utf-8" ) ) ;
504+ currentLineStderrBuffer = currentLineStderrBuffer . subarray (
505+ eolIndex + eolBuffer . length ,
506+ ) ;
507+ hasCreatedSubarray = true ;
508+ }
457509
458- // We have created a subarray, which means that the complete original buffer is now referenced
459- // by the subarray. We need to create a new buffer to avoid memory leaks.
460- if ( hasCreatedSubarray ) {
461- currentLineStderrBuffer = Buffer . from ( currentLineStderrBuffer ) ;
462- }
510+ // We have created a subarray, which means that the complete original buffer is now referenced
511+ // by the subarray. We need to create a new buffer to avoid memory leaks.
512+ if ( hasCreatedSubarray ) {
513+ currentLineStderrBuffer = Buffer . from ( currentLineStderrBuffer ) ;
463514 }
464- } ) ;
465- // Listen for process exit.
466- process . addListener ( "close" , ( code ) =>
467- reject ( new ExitCodeError ( code ) ) ,
468- ) ;
469- // Write the command followed by a null terminator.
470- process . stdin . write ( JSON . stringify ( args ) , "utf8" ) ;
471- process . stdin . write ( this . nullBuffer ) ;
472- } ) ;
473- // Join all the data together
474- const fullBuffer = Buffer . concat ( stdoutBuffers ) ;
475- // Make sure we remove the terminator;
476- const data = fullBuffer . toString ( "utf8" , 0 , fullBuffer . length - 1 ) ;
477- if ( ! silent ) {
478- void this . logger . log ( currentLineStderrBuffer . toString ( "utf8" ) ) ;
479- currentLineStderrBuffer = Buffer . alloc ( 0 ) ;
480- void this . logger . log ( "CLI command succeeded." ) ;
481- }
482- return data ;
483- } catch ( err ) {
484- // Kill the process if it isn't already dead.
485- this . killProcessIfRunning ( ) ;
515+ }
516+ } ;
517+ closeListener = ( code ) => {
518+ if ( handleNullTerminator ) {
519+ reject ( new ExitCodeError ( code ) ) ;
520+ } else {
521+ if ( code === 0 ) {
522+ resolve ( ) ;
523+ } else {
524+ reject ( new ExitCodeError ( code ) ) ;
525+ }
526+ }
527+ } ;
486528
487- // Report the error (if there is a stderr then use that otherwise just report the error code or nodejs error)
488- const cliError = getCliError (
489- err ,
490- stderrBuffers . length > 0
491- ? Buffer . concat ( stderrBuffers ) . toString ( "utf8" )
492- : undefined ,
493- description ,
494- args ,
495- ) ;
496- cliError . stack += getErrorStack ( err ) ;
497- throw cliError ;
498- } finally {
499- if ( ! silent && currentLineStderrBuffer . length > 0 ) {
500- void this . logger . log ( currentLineStderrBuffer . toString ( "utf8" ) ) ;
501- }
502- // Remove the listeners we set up.
503- process . stdout . removeAllListeners ( "data" ) ;
504- process . stderr . removeAllListeners ( "data" ) ;
505- process . removeAllListeners ( "close" ) ;
529+ // Start listening to stdout
530+ process . stdout . addListener ( "data" , stdoutListener ) ;
531+ // Listen to stderr
532+ process . stderr . addListener ( "data" , stderrListener ) ;
533+ // Listen for process exit.
534+ process . addListener ( "close" , closeListener ) ;
535+
536+ onListenStart ?.( process ) ;
537+ } ) ;
538+ // Join all the data together
539+ const fullBuffer = Buffer . concat ( stdoutBuffers ) ;
540+ // Make sure we remove the terminator
541+ const data = fullBuffer . toString (
542+ "utf8" ,
543+ 0 ,
544+ handleNullTerminator ? fullBuffer . length - 1 : fullBuffer . length ,
545+ ) ;
546+ if ( ! silent ) {
547+ void this . logger . log ( currentLineStderrBuffer . toString ( "utf8" ) ) ;
548+ currentLineStderrBuffer = Buffer . alloc ( 0 ) ;
549+ void this . logger . log ( "CLI command succeeded." ) ;
506550 }
551+ return data ;
552+ } catch ( err ) {
553+ // Report the error (if there is a stderr then use that otherwise just report the error code or nodejs error)
554+ const cliError = getCliError (
555+ err ,
556+ stderrBuffers . length > 0
557+ ? Buffer . concat ( stderrBuffers ) . toString ( "utf8" )
558+ : undefined ,
559+ description ,
560+ args ,
561+ ) ;
562+ cliError . stack += getErrorStack ( err ) ;
563+ throw cliError ;
507564 } finally {
508- this . commandInProcess = false ;
509- // start running the next command immediately
510- this . runNext ( ) ;
565+ if ( ! silent && currentLineStderrBuffer . length > 0 ) {
566+ void this . logger . log ( currentLineStderrBuffer . toString ( "utf8" ) ) ;
567+ }
568+ // Remove the listeners we set up.
569+ if ( stdoutListener ) {
570+ process . stdout . removeListener ( "data" , stdoutListener ) ;
571+ }
572+ if ( stderrListener ) {
573+ process . stderr . removeListener ( "data" , stderrListener ) ;
574+ }
575+ if ( closeListener ) {
576+ process . removeListener ( "close" , closeListener ) ;
577+ }
511578 }
512579 }
513580
0 commit comments