diff --git a/src/cli.ts b/src/cli.ts index fa0f279..75dabcf 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -228,24 +228,43 @@ function runRules(): void { // Command: trace // ───────────────────────────────────────────────────────────────────────────── -async function runTrace(symbolName: string, repoRoot: string): Promise { - console.log(chalk.bold.blue(`\nDiff-Guardian Trace: ${chalk.white(symbolName)}\n`)); - console.log(chalk.dim(' Scanning repo for importers...\n')); +async function runTrace(symbolName: string, repoRoot: string, jsonOutput : boolean = false,): Promise { + if(!jsonOutput) { + console.log(chalk.bold.blue(`\nDiff-Guardian Trace: ${chalk.white(symbolName)}\n`)); + console.log(chalk.dim(' Scanning repo for importers...\n')); + } - const tracerConfig = createDefaultTracerConfig(repoRoot, 'HEAD'); + const tracerConfig = createDefaultTracerConfig( + repoRoot, + 'HEAD', + { + jsonOutput, + } + ); const scanner = new JITScanner(tracerConfig); try { const importers = await scanner.scan(symbolName, ''); if (importers.length === 0) { - console.log(chalk.yellow(` No importers found for "${symbolName}".`)); - console.log(chalk.dim(' The symbol may not be exported, or no files import it.')); - console.log(); + if (jsonOutput) { + console.log(JSON.stringify({ + symbol: symbolName, + importers: [], + totalImports: 0, + totalFiles: 0, + }, null, 2)); + } else { + console.log(chalk.yellow(` No importers found for "${symbolName}".`)); + console.log(chalk.dim(' The symbol may not be exported, or no files import it.')); + console.log(); + } return; } - console.log(chalk.green.bold(` 📍 ${symbolName} — ${importers.length} importer(s) found\n`)); + if(!jsonOutput) { + console.log(chalk.green.bold(` 📍 ${symbolName} — ${importers.length} importer(s) found\n`)); + } // Group by file const byFile = new Map(); @@ -255,6 +274,18 @@ async function runTrace(symbolName: string, repoRoot: string): Promise { byFile.set(imp.filePath, existing); } + const traceResult = { + symbol: symbolName, + importers, + totalImports: importers.length, + totalFiles: byFile.size, + }; + + if (jsonOutput) { + console.log(JSON.stringify(traceResult, null, 2)); + return; + } + for (const [file, imps] of byFile) { console.log(` ${chalk.cyan(file)}`); for (const imp of imps) { @@ -425,13 +456,14 @@ async function runSmartDefault( async function main() { const args = minimist(process.argv.slice(2), { - boolean: ['help', 'staged'], + boolean: ['help', 'staged', 'json'], string: ['report-file'], alias: { h: 'help' }, }); const command = args._[0]; const reportFile = args['report-file'] || undefined; + const jsonOutput = args.json || false; // ── Hook context (set by husky hooks via DG_HOOK env var) ──────────────── const hookContext = (process.env.DG_HOOK as 'pre-push' | 'pre-merge-commit' | 'post-merge') || undefined; @@ -472,7 +504,7 @@ async function main() { console.log(' Example: npx dg trace processPayment\n'); process.exit(1); } - await runTrace(symbolName, repoRoot); + await runTrace(symbolName, repoRoot, jsonOutput); process.exit(0); } diff --git a/src/core/types.ts b/src/core/types.ts index bb103ef..f894820 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -386,4 +386,6 @@ export interface TracerConfig { // if false, also trace warnings for completeness repoRoot: string; // absolute path to repo root headSha: string; // git ref for committed content (used by git grep) + quiet?: boolean; + jsonOutput?: boolean; } \ No newline at end of file diff --git a/src/tracer/scanner.ts b/src/tracer/scanner.ts index b28b951..3021975 100644 --- a/src/tracer/scanner.ts +++ b/src/tracer/scanner.ts @@ -387,9 +387,11 @@ export class JITScanner { } if (barrelsProcessed > 0) { - console.log( - `[scanner] Traversed ${barrelsProcessed} barrel file(s) for "${symbolName}"` - ); + if(!this.config.jsonOutput) { + console.log( + `[scanner] Traversed ${barrelsProcessed} barrel file(s) for "${symbolName}"` + ); + } } } @@ -518,6 +520,7 @@ export function createDefaultTracerConfig( maxBarrelDepth: 10, maxTracerFiles: 100, traceOnlyBreaking: true, + jsonOutput: false, repoRoot, headSha, ...overrides,