11import { CLI } from '@snyk/error-catalog-nodejs-public' ;
22import * as childProcess from 'child_process' ;
33import { debug as Debug } from 'debug' ;
4+ import { StringDecoder } from 'string_decoder' ;
5+ import { abridgeErrorMessage } from './error-format' ;
46
57const debug = Debug ( 'snyk:go-bridge' ) ;
68
79const SNYK_INTERNAL_CLI_EXECUTABLE_PATH_ENV =
810 'SNYK_INTERNAL_CLI_EXECUTABLE_PATH' ;
911const MAX_BUFFER = 50 * 1024 * 1024 ;
12+ const GO_BRIDGE_STDERR_PREFIX = '[go-bridge] ' ;
13+ const STDERR_TRUNCATION_ELLIPSIS = ' ...(stderr truncated) ... ' ;
14+
15+ interface PrefixedChunkResult {
16+ chunk : string ;
17+ isAtLineStart : boolean ;
18+ }
1019
1120export interface GoCommandResult {
1221 exitCode : number ;
@@ -29,6 +38,9 @@ export interface GoCommandResult {
2938 * - The child process fails to spawn (e.g., binary not found)
3039 * - stdout exceeds the maximum buffer size
3140 *
41+ * stderr output is soft-capped: once it reaches the maximum buffer size, it is
42+ * truncated with an ellipsis marker and no further stderr is accumulated.
43+ *
3244 * @param args - The arguments to pass to the Go Snyk CLI binary (e.g., ['depgraph', '--file=uv.lock'])
3345 * @param options - Optional settings for the child process
3446 * @returns A result object with the exitCode, stdout, and stderr
@@ -49,13 +61,53 @@ export function execGoCommand(
4961 }
5062
5163 debug ( 'executing Go command: %s %s' , execPath , args . join ( ' ' ) ) ;
64+ const shouldStreamStderr = args . includes ( '--debug' ) ;
5265 const commandEnv = restoreSystemEnvironment ( {
5366 ...process . env ,
5467 } ) ;
5568
5669 return new Promise ( ( resolve , reject ) => {
5770 let stdout = '' ;
5871 let stderr = '' ;
72+ let stderrSize = 0 ;
73+ let isStderrTruncated = false ;
74+ let isStderrAtLineStart = true ;
75+ const stderrDecoder = new StringDecoder ( 'utf8' ) ;
76+
77+ const appendStderrChunk = ( stderrChunk : string ) : void => {
78+ if ( ! stderrChunk ) {
79+ return ;
80+ }
81+
82+ if ( shouldStreamStderr ) {
83+ const result = prefixChunkLines (
84+ stderrChunk ,
85+ GO_BRIDGE_STDERR_PREFIX ,
86+ isStderrAtLineStart ,
87+ ) ;
88+ isStderrAtLineStart = result . isAtLineStart ;
89+ process . stderr . write ( result . chunk ) ;
90+ }
91+
92+ if ( isStderrTruncated ) {
93+ return ;
94+ }
95+
96+ const stderrChunkSize = Buffer . byteLength ( stderrChunk , 'utf8' ) ;
97+ if ( stderrSize + stderrChunkSize > MAX_BUFFER ) {
98+ stderr = abridgeErrorMessage (
99+ `${ stderr } ${ stderrChunk } ` ,
100+ MAX_BUFFER ,
101+ STDERR_TRUNCATION_ELLIPSIS ,
102+ ) ;
103+ stderrSize = Buffer . byteLength ( stderr , 'utf8' ) ;
104+ isStderrTruncated = true ;
105+ return ;
106+ }
107+
108+ stderr += stderrChunk ;
109+ stderrSize += stderrChunkSize ;
110+ } ;
59111
60112 const proc = childProcess . spawn ( execPath , args , {
61113 cwd : options ?. cwd ,
@@ -78,8 +130,10 @@ export function execGoCommand(
78130 }
79131
80132 if ( proc . stderr ) {
81- proc . stderr . on ( 'data' , ( data : Buffer ) => {
82- stderr += data ;
133+ proc . stderr . on ( 'data' , ( data : Buffer | string ) => {
134+ const stderrChunk =
135+ typeof data === 'string' ? data : stderrDecoder . write ( data ) ;
136+ appendStderrChunk ( stderrChunk ) ;
83137 } ) ;
84138 }
85139
@@ -93,6 +147,9 @@ export function execGoCommand(
93147 } ) ;
94148
95149 proc . on ( 'close' , ( code ) => {
150+ const trailingStderrChunk = stderrDecoder . end ( ) ;
151+ appendStderrChunk ( trailingStderrChunk ) ;
152+
96153 debug ( 'Go command exited with code %d' , code ) ;
97154 resolve ( { exitCode : code ?? 1 , stdout, stderr } ) ;
98155 } ) ;
@@ -123,3 +180,19 @@ function restoreSystemEnvironment(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
123180 }
124181 return env ;
125182}
183+
184+ function prefixChunkLines (
185+ chunk : string ,
186+ prefix : string ,
187+ isAtLineStart : boolean ,
188+ ) : PrefixedChunkResult {
189+ if ( ! chunk ) {
190+ return { chunk, isAtLineStart } ;
191+ }
192+
193+ const prefixedChunkBody = chunk . replace ( / \n (? ! $ ) / g, `\n${ prefix } ` ) ;
194+ return {
195+ chunk : isAtLineStart ? `${ prefix } ${ prefixedChunkBody } ` : prefixedChunkBody ,
196+ isAtLineStart : chunk . endsWith ( '\n' ) ,
197+ } ;
198+ }
0 commit comments