Skip to content

Commit 14f1eed

Browse files
committed
Switch to STDERR and STDOUT instead of console.log to split info and output
1 parent caa79c3 commit 14f1eed

File tree

4 files changed

+29
-31
lines changed

4 files changed

+29
-31
lines changed

src/cli.ts

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -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,

src/malwareMatcher.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -334,11 +334,9 @@ export async function uploadSarifPerRepo(opts: {
334334
sarif: sarifB64,
335335
tool_name: our_tool_name
336336
});
337-
// eslint-disable-next-line no-console
338-
console.log(`Uploaded SARIF for ${repo}`);
337+
process.stderr.write(`Uploaded SARIF for ${repo}\n`);
339338
} catch (e) {
340-
// eslint-disable-next-line no-console
341-
console.error(`Failed to upload SARIF for ${repo}: ${e instanceof Error ? e.message : String(e)}`);
339+
process.stderr.write(`Failed to upload SARIF for ${repo}: ${e instanceof Error ? e.message : String(e)}\n`);
342340
}
343341
}
344342
}

src/sbomCollector.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ export class SbomCollector {
9898
// find just the path for a single org, if given
9999
const loadPath = this.opts.org ? `${this.opts.loadFromDir}/${this.opts.org}` : this.opts.loadFromDir;
100100

101-
if (!this.opts.quiet) console.log(chalk.blue(`Loading SBOMs from cache at ${loadPath}`));
101+
if (!this.opts.quiet) process.stderr.write(chalk.blue(`Loading SBOMs from cache at ${loadPath}`) + "\n");
102102

103103
try {
104104
this.sboms = readAll(loadPath);
@@ -129,7 +129,7 @@ export class SbomCollector {
129129
}
130130

131131
if (this.opts.enterprise && !this.opts.quiet) {
132-
console.log(chalk.blue(`Getting list of organizations for enterprise ${this.opts.enterprise}`));
132+
process.stderr.write(chalk.blue(`Getting list of organizations for enterprise ${this.opts.enterprise}`) + "\n");
133133
}
134134

135135
const orgs = this.opts.org ? [this.opts.org] : await this.listEnterpriseOrgs(this.opts.enterprise!);
@@ -139,7 +139,7 @@ export class SbomCollector {
139139
const orgRepoMap: Record<string, { name: string; pushed_at?: string; updated_at?: string; default_branch?: string }[]> = {};
140140
let totalRepos = 0;
141141
for (const org of orgs) {
142-
if (!this.opts.quiet) console.log(chalk.blue(`Listing repositories for org ${org}`));
142+
if (!this.opts.quiet) process.stderr.write(chalk.blue(`Listing repositories for org ${org}`) + "\n");
143143
if (this.opts.lightDelayMs) await new Promise(r => setTimeout(r, this.opts.lightDelayMs));
144144
const repos = await this.listOrgRepos(org);
145145
orgRepoMap[org] = repos;
@@ -167,7 +167,7 @@ export class SbomCollector {
167167

168168
for (const org of orgs) {
169169
if (!this.opts.showProgressBar && !this.opts.quiet) {
170-
console.log(chalk.blue(`Collecting SBOMs for org ${org}`));
170+
process.stderr.write(chalk.blue(`Collecting SBOMs for org ${org}`) + "\n");
171171
}
172172
const repos = orgRepoMap[org];
173173
const repoNames = new Set(repos.map(r => r.name));

src/test-fixture-match.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ const cache = JSON.parse(fs.readFileSync(cachePath, "utf8"));
1212
const advisories: MalwareAdvisoryNode[] = cache.advisories;
1313

1414
const matches = matchMalware(advisories, sboms);
15-
console.log("Matches:");
15+
process.stdout.write("Matches:\n");
1616
for (const m of matches) {
17-
console.log(`${m.repo} => ${m.purl} matched advisory ${m.advisoryGhsaId} range ${m.vulnerableVersionRange}`);
17+
process.stdout.write(`${m.repo} => ${m.purl} matched advisory ${m.advisoryGhsaId} range ${m.vulnerableVersionRange}\n`);
1818
}
1919
if (!matches.length) {
2020
console.error("No matches found - expected chalk 5.6.1");

0 commit comments

Comments
 (0)