#!/usr/bin/env node /** * Config-Audit Fix CLI * Standalone entry point for running fixes without the command. * Usage: node fix-cli.mjs [--apply] [--global] [--json] * Dry-run by default — must pass --apply to write changes. * Zero external dependencies. */ import { resolve } from 'node:path'; import { runAllScanners } from './scan-orchestrator.mjs'; import { planFixes, applyFixes, verifyFixes } from './fix-engine.mjs'; import { createBackup } from './lib/backup.mjs'; async function main() { const args = process.argv.slice(2); let targetPath = '.'; let apply = false; let jsonMode = false; let includeGlobal = false; for (let i = 0; i < args.length; i++) { if (args[i] === '--apply') { apply = true; } else if (args[i] === '--json') { jsonMode = true; } else if (args[i] === '--global') { includeGlobal = true; } else if (!args[i].startsWith('-')) { targetPath = args[i]; } } const resolvedPath = resolve(targetPath); if (!jsonMode) { process.stderr.write(`Config-Audit Fix CLI v2.1.0\n`); process.stderr.write(`Target: ${resolvedPath}\n`); process.stderr.write(`Mode: ${apply ? 'APPLY' : 'DRY-RUN'}\n\n`); process.stderr.write(`Scanning...\n`); } // 1. Run all scanners const envelope = await runAllScanners(targetPath, { includeGlobal }); // 2. Plan fixes const { fixes, skipped, manual } = planFixes(envelope); if (!jsonMode) { process.stderr.write(`\n`); process.stderr.write(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`); process.stderr.write(` Config-Audit Fix Plan\n`); process.stderr.write(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n`); if (fixes.length > 0) { process.stderr.write(` Auto-fixable (${fixes.length}):\n`); for (let i = 0; i < fixes.length; i++) { process.stderr.write(` ${i + 1}. [${fixes[i].findingId}] ${fixes[i].description}\n`); } } else { process.stderr.write(` No auto-fixable issues found.\n`); } if (manual.length > 0) { process.stderr.write(`\n Manual (${manual.length}):\n`); for (let i = 0; i < manual.length; i++) { process.stderr.write(` ${fixes.length + i + 1}. [${manual[i].findingId}] ${manual[i].title}\n`); } } if (skipped.length > 0) { process.stderr.write(`\n Skipped (${skipped.length}): could not generate fix plan\n`); } process.stderr.write(`\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`); } // 3. Apply or dry-run let applied = []; let failed = []; let verified = []; let regressions = []; let backupId = null; if (fixes.length === 0) { if (jsonMode) { const output = { planned: [], applied: [], failed: [], verified: [], regressions: [], manual, backupId: null }; process.stdout.write(JSON.stringify(output, null, 2) + '\n'); } process.exit(0); } if (apply) { // Create backup first const filesToBackup = [...new Set(fixes.filter(f => f.type !== 'file-rename').map(f => f.file))]; const backup = createBackup(filesToBackup); backupId = backup.backupId; if (!jsonMode) { process.stderr.write(`\n Backup created: ${backup.backupPath}\n`); process.stderr.write(` Applying ${fixes.length} fixes...\n\n`); } const result = await applyFixes(fixes, { dryRun: false, backupDir: backup.backupPath }); applied = result.applied; failed = result.failed; if (!jsonMode) { process.stderr.write(` Results: ${applied.length} applied, ${failed.length} failed\n`); if (failed.length > 0) { for (const f of failed) { process.stderr.write(` FAILED: [${f.findingId}] ${f.error}\n`); } } } // 4. Verify if (applied.length > 0) { if (!jsonMode) { process.stderr.write(`\n Verifying...\n`); } const verification = await verifyFixes(envelope, applied); verified = verification.verified; regressions = verification.regressions; if (!jsonMode) { process.stderr.write(` Verified: ${verified.length}/${applied.length}\n`); if (regressions.length > 0) { process.stderr.write(` Regressions: ${regressions.join(', ')}\n`); } process.stderr.write(`\n Rollback: node scanners/rollback-cli.mjs ${backupId}\n`); } } } else { // Dry-run mode const result = await applyFixes(fixes, { dryRun: true }); applied = result.applied; if (!jsonMode) { process.stderr.write(`\n Dry-run complete. Pass --apply to execute.\n`); } } // JSON output if (jsonMode) { const output = { planned: fixes.map(f => ({ findingId: f.findingId, file: f.file, type: f.type, description: f.description, })), applied: applied.map(a => ({ findingId: a.findingId, file: a.file, status: a.status, })), failed: failed.map(f => ({ findingId: f.findingId, file: f.file, status: f.status, error: f.error, })), verified, regressions, manual: manual.map(m => ({ findingId: m.findingId, title: m.title, recommendation: m.recommendation, })), backupId, }; process.stdout.write(JSON.stringify(output, null, 2) + '\n'); } } // 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); }); }