@@ -99,10 +99,10 @@ async function main() {
9999 caBundlePath : argv [ "ca-bundle" ] as string | undefined ,
100100 } ) ;
101101
102- if ( ! quiet ) console . log ( chalk . cyan ( offline ? "Loading SBOMs from cache..." : "Collecting SBOMs from cache & GitHub..." ) ) ;
102+ if ( ! quiet ) process . stderr . write ( chalk . cyan ( offline ? "Loading SBOMs from cache..." : "Collecting SBOMs from cache & GitHub..." ) + "\n" ) ;
103103 const sboms = await collector . collect ( ) ;
104104 const summary = collector . getSummary ( ) ;
105- if ( ! quiet ) console . log ( chalk . green ( `Done. Success: ${ summary . successCount } / ${ summary . repositoryCount } . Failed: ${ summary . failedCount } . Cached: ${ summary . skippedCount } ` ) ) ;
105+ if ( ! quiet ) process . stderr . write ( chalk . green ( `Done. Success: ${ summary . successCount } / ${ summary . repositoryCount } . Failed: ${ summary . failedCount } . Cached: ${ summary . skippedCount } ` ) + "\n" ) ;
106106
107107 const mas = new MalwareAdvisorySync ( {
108108 token : token ! ,
@@ -114,10 +114,10 @@ async function main() {
114114
115115 if ( argv [ "sync-malware" ] ) {
116116
117- if ( ! quiet ) console . log ( chalk . cyan ( "Syncing malware advisories from GitHub Advisory Database..." ) ) ;
117+ if ( ! quiet ) process . stderr . write ( chalk . cyan ( "Syncing malware advisories from GitHub Advisory Database..." ) + "\n" ) ;
118118
119119 const { added, updated, total } = await mas . sync ( ) ;
120- if ( ! quiet ) console . log ( chalk . green ( `Malware advisories sync complete. Added: ${ added } , Updated: ${ updated } , Total cached: ${ total } ` ) ) ;
120+ if ( ! quiet ) process . stderr . write ( chalk . green ( `Malware advisories sync complete. Added: ${ added } , Updated: ${ updated } , Total cached: ${ total } ` ) + "\n" ) ;
121121 }
122122
123123 let malwareMatches : import ( "./malwareMatcher.js" ) . MalwareMatch [ ] | undefined ;
@@ -132,32 +132,32 @@ async function main() {
132132 if ( matcher ) {
133133 const { kept, ignored } = matcher . filter ( malwareMatches ) ;
134134 if ( ! argv . quiet ) {
135- console . log ( chalk . yellow ( `Ignored ${ ignored . length } malware match(es) via ignore file; ${ kept . length } remaining.` ) ) ;
135+ process . stderr . write ( chalk . yellow ( `Ignored ${ ignored . length } malware match(es) via ignore file; ${ kept . length } remaining.` ) + "\n" ) ;
136136 }
137137 malwareMatches = kept ;
138138 // If writing SARIF we intentionally only report kept matches; optionally we could emit a log of ignored reasons.
139139 } else if ( ! argv . quiet ) {
140- console . log ( chalk . yellow ( `Ignore file '${ argv [ "ignore-file" ] } ' not found or failed to parse; proceeding without filtering.` ) ) ;
140+ process . stderr . write ( chalk . yellow ( `Ignore file '${ argv [ "ignore-file" ] } ' not found or failed to parse; proceeding without filtering.` ) + "\n" ) ;
141141 }
142142 } catch ( e ) {
143143 console . error ( chalk . red ( `Failed applying ignore file: ${ ( e as Error ) . message } ` ) ) ;
144144 }
145145 }
146- if ( ! quiet ) console . log ( chalk . magenta ( `Malware matches found: ${ malwareMatches ?. length ?? 0 } ` ) ) ;
146+ if ( ! quiet ) process . stderr . write ( chalk . magenta ( `Malware matches found: ${ malwareMatches ?. length ?? 0 } ` ) + "\n" ) ;
147147 if ( malwareMatches ) {
148148 if ( ! quiet ) {
149149 for ( const m of malwareMatches ) {
150- console . log ( `${ m . repo } :: ${ m . purl } => ${ m . advisoryGhsaId } (${ m . vulnerableVersionRange ?? "(no range)" } ) {advisory: ${ m . reason } } ${ m . advisoryPermalink } ` ) ;
150+ process . stdout . write ( `${ m . repo } :: ${ m . purl } => ${ m . advisoryGhsaId } (${ m . vulnerableVersionRange ?? "(no range)" } ) {advisory: ${ m . reason } } ${ m . advisoryPermalink } \n ` ) ;
151151 }
152152 }
153153 if ( argv . sarifDir ) {
154154 const sarifMap = buildSarifPerRepo ( malwareMatches , mas . getAdvisories ( ) ) ;
155155 writeSarifFiles ( argv . sarifDir as string , sarifMap ) ;
156156 if ( sarifMap . size === 0 ) {
157- if ( ! quiet ) console . log ( chalk . yellow ( "No SARIF files generated." ) ) ;
157+ if ( ! quiet ) process . stderr . write ( chalk . yellow ( "No SARIF files generated." ) + "\n" ) ;
158158 return ;
159159 }
160- if ( ! quiet ) console . log ( chalk . green ( `Wrote SARIF for ${ sarifMap . size } repos to ${ argv . sarifDir } ` ) ) ;
160+ if ( ! quiet ) process . stderr . write ( chalk . green ( `Wrote SARIF for ${ sarifMap . size } repos to ${ argv . sarifDir } ` ) + "\n" ) ;
161161 if ( argv . uploadSarif ) {
162162 if ( ! token ) console . error ( chalk . red ( "Token required for SARIF upload" ) ) ;
163163 else await uploadSarifPerRepo ( { sarifDir : argv . sarifDir as string , matches : malwareMatches , advisories : mas . getAdvisories ( ) , sboms, token, baseUrl : argv [ "base-url" ] as string | undefined , caBundlePath : argv [ "ca-bundle" ] as string | undefined } ) ;
@@ -167,19 +167,19 @@ async function main() {
167167 }
168168 // Incremental write now handled inside collector; retain legacy behavior only if user wants to force a re-write
169169 if ( ! quiet && argv . syncSboms && argv [ "sbom-cache" ] && summary . repositoryCount === summary . skippedCount ) {
170- console . log ( chalk . blue ( "All repositories reused from cache (no new SBOM writes)." ) ) ;
170+ process . stderr . write ( chalk . blue ( "All repositories reused from cache (no new SBOM writes)." ) + "\n" ) ;
171171 }
172172
173173 const runSearch = ( purls : string [ ] ) => {
174174 const results = collector . searchByPurlsWithReasons ( purls ) ;
175- if ( ! quiet ) console . log ( chalk . magenta ( `Search results for ${ purls . length } purl(s):` ) ) ;
175+ if ( ! quiet ) process . stderr . write ( chalk . magenta ( `Search results for ${ purls . length } purl(s):` ) + "\n" ) ;
176176 if ( ! results . size ) {
177- if ( ! quiet ) console . log ( "No matches." ) ;
177+ if ( ! quiet ) process . stdout . write ( "No matches.\n " ) ;
178178 return ;
179179 }
180180 for ( const [ repo , entries ] of results . entries ( ) ) {
181- console . log ( chalk . bold ( repo ) ) ;
182- for ( const { purl, reason } of entries ) console . log ( ` - ${ purl } {query: ${ reason } }` ) ;
181+ process . stdout . write ( chalk . bold ( repo ) + "\n" ) ;
182+ for ( const { purl, reason } of entries ) process . stdout . write ( ` - ${ purl } {query: ${ reason } }\n ` ) ;
183183 }
184184 } ;
185185 // Load queries from file if provided
@@ -193,7 +193,7 @@ async function main() {
193193 if ( ! line || line . startsWith ( "#" ) ) continue ;
194194 filePurls . push ( line ) ;
195195 }
196- if ( filePurls . length && ! quiet ) console . log ( chalk . cyan ( `Loaded ${ filePurls . length } PURL query(ies) from file` ) ) ;
196+ if ( filePurls . length && ! quiet ) process . stderr . write ( chalk . cyan ( `Loaded ${ filePurls . length } PURL query(ies) from file` ) + "\n" ) ;
197197 } catch ( e ) {
198198 console . error ( chalk . red ( `Failed to read purl file: ${ e instanceof Error ? e . message : String ( e ) } ` ) ) ;
199199 process . exit ( 1 ) ;
@@ -217,7 +217,7 @@ async function main() {
217217 if ( malwareMatches ) existing . malwareMatches = existing . malwareMatches || malwareMatches ; // preserve if already set
218218 const payload = JSON . stringify ( existing , null , 2 ) + "\n" ;
219219 fs . writeFileSync ( argv . outputFile as string , payload , "utf8" ) ;
220- if ( ! quiet ) console . log ( chalk . green ( `Wrote search JSON to ${ argv . outputFile } ` ) ) ;
220+ if ( ! quiet ) process . stderr . write ( chalk . green ( `Wrote search JSON to ${ argv . outputFile } ` ) + "\n" ) ;
221221 } catch ( e ) {
222222 console . error ( chalk . red ( `Failed to write output file: ${ e instanceof Error ? e . message : String ( e ) } ` ) ) ;
223223 process . exit ( 1 ) ;
@@ -294,7 +294,7 @@ async function main() {
294294 if ( argv . outFile ) {
295295 try {
296296 fs . writeFileSync ( argv . outFile as string , csvPayload , "utf8" ) ;
297- if ( ! quiet ) console . log ( chalk . green ( `Wrote CSV to ${ argv . outFile } ` ) ) ;
297+ if ( ! quiet ) process . stderr . write ( chalk . green ( `Wrote CSV to ${ argv . outFile } ` ) + "\n" ) ;
298298 } catch ( e ) {
299299 console . error ( chalk . red ( `Failed to write CSV file: ${ e instanceof Error ? e . message : String ( e ) } ` ) ) ;
300300 process . exit ( 1 ) ;
@@ -314,7 +314,7 @@ async function main() {
314314 }
315315 existing . malwareMatches = malwareMatches ;
316316 fs . writeFileSync ( argv . outputFile as string , JSON . stringify ( existing , null , 2 ) + "\n" , "utf8" ) ;
317- if ( ! quiet ) console . log ( chalk . green ( `Wrote malware matches JSON to ${ argv . outputFile } ` ) ) ;
317+ if ( ! quiet ) process . stderr . write ( chalk . green ( `Wrote malware matches JSON to ${ argv . outputFile } ` ) + "\n" ) ;
318318 } catch ( e ) {
319319 console . error ( chalk . red ( `Failed to write malware matches to output file: ${ e instanceof Error ? e . message : String ( e ) } ` ) ) ;
320320 }
@@ -324,8 +324,8 @@ async function main() {
324324 // Prefer readline for native shell history (arrow up/down) so users can edit previous queries.
325325 if ( process . stdin . isTTY && process . stdout . isTTY ) {
326326 if ( ! quiet ) {
327- console . log ( chalk . cyan ( "Interactive mode: enter PURL queries (supports semver ranges, wildcards, version ranges)." ) ) ;
328- console . log ( chalk . cyan ( "Tips: Use arrow keys for history. Blank line or Ctrl+C on empty prompt exits. Ctrl+C on a non-empty line clears it." ) ) ;
327+ process . stderr . write ( chalk . cyan ( "Interactive mode: enter PURL queries (supports semver ranges, wildcards, version ranges)." ) + "\n" ) ;
328+ process . stderr . write ( chalk . cyan ( "Tips: Use arrow keys for history. Blank line or Ctrl+C on empty prompt exits. Ctrl+C on a non-empty line clears it." ) + "\n" ) ;
329329 }
330330 const rl = readline . createInterface ( {
331331 input : process . stdin ,
0 commit comments