ktg-plugin-marketplace/plugins/config-audit/scanners/drift-cli.mjs

130 lines
4.5 KiB
JavaScript

#!/usr/bin/env node
/**
* Config-Audit Drift CLI
* Compare current configuration against a saved baseline.
* Usage:
* node drift-cli.mjs <path> --save [--name my-baseline]
* node drift-cli.mjs <path> [--baseline my-baseline] [--json]
* node drift-cli.mjs --list
* Zero external dependencies.
*/
import { resolve } from 'node:path';
import { runAllScanners } from './scan-orchestrator.mjs';
import { diffEnvelopes, formatDiffReport } from './lib/diff-engine.mjs';
import { saveBaseline, loadBaseline, listBaselines } from './lib/baseline.mjs';
async function main() {
const args = process.argv.slice(2);
let targetPath = '.';
let baselineName = 'default';
let save = false;
let list = false;
let jsonMode = false;
let includeGlobal = false;
for (let i = 0; i < args.length; i++) {
if (args[i] === '--save') {
save = true;
} else if (args[i] === '--name' && args[i + 1]) {
baselineName = args[++i];
} else if (args[i] === '--baseline' && args[i + 1]) {
baselineName = args[++i];
} else if (args[i] === '--list') {
list = true;
} else if (args[i] === '--json') {
jsonMode = true;
} else if (args[i] === '--global') {
includeGlobal = true;
} else if (!args[i].startsWith('-')) {
targetPath = args[i];
}
}
// --- List mode ---
if (list) {
const result = await listBaselines();
if (jsonMode) {
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
} else {
if (result.baselines.length === 0) {
process.stderr.write('No baselines saved.\n');
process.stderr.write('Save one with: node drift-cli.mjs <path> --save\n');
} else {
process.stderr.write('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
process.stderr.write(' Saved Baselines\n');
process.stderr.write('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n');
for (const b of result.baselines) {
process.stderr.write(` ${b.name.padEnd(20)} ${b.findingCount} findings ${b.savedAt}\n`);
}
process.stderr.write('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
}
}
process.exit(0);
}
// --- Save mode ---
if (save) {
if (!jsonMode) {
process.stderr.write(`Config-Audit Drift CLI v2.1.0\n`);
process.stderr.write(`Saving baseline "${baselineName}" for ${resolve(targetPath)}\n\n`);
}
const envelope = await runAllScanners(targetPath, { includeGlobal });
const result = await saveBaseline(envelope, baselineName);
if (jsonMode) {
process.stdout.write(JSON.stringify({ saved: true, name: result.name, path: result.path }, null, 2) + '\n');
} else {
process.stderr.write(`\nBaseline "${result.name}" saved to ${result.path}\n`);
process.stderr.write(`Findings: ${envelope.aggregate.total_findings}\n`);
}
process.exit(0);
}
// --- Drift mode (default) ---
if (!jsonMode) {
process.stderr.write(`Config-Audit Drift CLI v2.1.0\n`);
process.stderr.write(`Target: ${resolve(targetPath)}\n`);
process.stderr.write(`Baseline: ${baselineName}\n\n`);
}
// Load baseline
const baseline = await loadBaseline(baselineName);
if (!baseline) {
if (jsonMode) {
process.stdout.write(JSON.stringify({ error: `Baseline "${baselineName}" not found. Save one with --save.` }, null, 2) + '\n');
} else {
process.stderr.write(`Baseline "${baselineName}" not found.\n`);
process.stderr.write(`Save one first: node drift-cli.mjs <path> --save\n`);
}
process.exit(1);
}
// Run current scan
const current = await runAllScanners(targetPath, { includeGlobal });
// Diff
const diff = diffEnvelopes(baseline, current);
if (jsonMode) {
process.stdout.write(JSON.stringify(diff, null, 2) + '\n');
} else {
const report = formatDiffReport(diff);
process.stderr.write('\n' + report + '\n');
}
// Exit code: 0=stable/improving, 1=degrading
if (diff.summary.trend === 'degrading') process.exit(1);
process.exit(0);
}
// Only run CLI if invoked directly
const isDirectRun = process.argv[1] && resolve(process.argv[1]) === resolve(new URL(import.meta.url).pathname);
if (isDirectRun) {
main().catch(err => {
process.stderr.write(`Fatal: ${err.message}\n`);
process.exit(3);
});
}