feat: initial open marketplace with llm-security, config-audit, ultraplan-local
This commit is contained in:
commit
f93d6abdae
380 changed files with 65935 additions and 0 deletions
186
plugins/config-audit/scanners/fix-cli.mjs
Normal file
186
plugins/config-audit/scanners/fix-cli.mjs
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Config-Audit Fix CLI
|
||||
* Standalone entry point for running fixes without the command.
|
||||
* Usage: node fix-cli.mjs <path> [--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);
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue