UNPKG

supamend

Version:

Pluggable DevSecOps Security Scanner with 10+ scanners and multiple reporting channels

188 lines 8.29 kB
#!/usr/bin/env node "use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const commander_1 = require("commander"); const supamend_1 = __importDefault(require("./core/supamend")); const git_manager_1 = require("./core/git-manager"); const interactive_1 = require("./cli/interactive"); const chalk_1 = __importDefault(require("chalk")); const ora_1 = __importDefault(require("ora")); const fs = __importStar(require("fs-extra")); const program = new commander_1.Command(); program .name('supamend') .description('Pluggable DevSecOps Security Bot') .version('1.0.0'); program .command('scan') .description('Scan a repository for security issues') .option('--repo <url>', 'Repository URL to scan (if not specified, will use current git repository)') .option('--token <token>', 'GitHub Personal Access Token') .option('--branch <branch>', 'Branch to scan (default: main)') .option('--scanners <scanners>', 'Comma-separated list of scanners to use') .option('--reporters <reporters>', 'Comma-separated list of reporters to use') .option('--config <path>', 'Configuration file path') .option('--output <path>', 'Output file path for results') .option('--verbose', 'Enable verbose logging') .option('-i, --interactive', 'Run in interactive mode') .action(async (options) => { // Handle interactive mode if (options.interactive) { const interactive = new interactive_1.InteractiveCLI(); await interactive.run(); return; } const spinner = (0, ora_1.default)('Initializing SupaMend...').start(); try { let configDefaults = {}; if (options.config && await fs.pathExists(options.config)) { const configContent = await fs.readFile(options.config, 'utf-8'); const configJson = JSON.parse(configContent); configDefaults = { repo: configJson.repo, token: configJson.token, branch: configJson.branch, scanners: configJson.scanners ? configJson.scanners.filter((s) => s.enabled).map((s) => s.name) : undefined, reporters: configJson.reporters ? configJson.reporters.filter((r) => r.enabled).map((r) => r.name) : undefined, output: configJson.output, verbose: configJson.verbose }; } // Detect current git repository if no repo is specified let detectedRepo = null; let detectedBranch = null; let useLocalPath = false; if (!options.repo && !configDefaults.repo) { const gitManager = new git_manager_1.GitManager(); const isGitRepo = await gitManager.isGitRepository(); if (isGitRepo) { // Use local path directly detectedRepo = process.cwd(); useLocalPath = true; console.log(chalk_1.default.blue(`Detected local git repository: ${detectedRepo}`)); } else { throw new Error('No repository URL specified (--repo) and current directory is not a git repository.'); } } // Merge CLI options (priority) with config defaults and detected values const scanOptions = { repo: options.repo || configDefaults.repo || detectedRepo, token: options.token || configDefaults.token, branch: options.branch || configDefaults.branch || detectedBranch || 'main', scanners: (options.scanners ? options.scanners.split(',').map((s) => s.trim()) : configDefaults.scanners || []), reporters: (options.reporters ? options.reporters.split(',').map((r) => r.trim()) : configDefaults.reporters), config: options.config, output: options.output || configDefaults.output, verbose: options.verbose !== undefined ? options.verbose : configDefaults.verbose }; // Validate required fields if (!scanOptions.repo) throw new Error('Repository URL (--repo) must be specified via CLI, config file, or detected from current git repository.'); // Scanners will be auto-detected if not specified if (!scanOptions.reporters || !scanOptions.reporters.length) throw new Error('At least one reporter (--reporters) must be specified via CLI or config file.'); const supamend = new supamend_1.default(); spinner.text = 'Running security scan...'; const results = await supamend.scan(scanOptions); spinner.succeed(`Scan completed! Found ${results.length} issues.`); if (scanOptions.output) { await fs.writeJson(scanOptions.output, results, { spaces: 2 }); console.log(chalk_1.default.green(`Results saved to ${scanOptions.output}`)); } // Display summary const severityCounts = results.reduce((acc, result) => { acc[result.severity] = (acc[result.severity] || 0) + 1; return acc; }, {}); console.log(chalk_1.default.blue('\nSummary:')); Object.entries(severityCounts).forEach(([severity, count]) => { const color = severity === 'critical' ? 'red' : severity === 'high' ? 'yellow' : severity === 'medium' ? 'blue' : 'green'; console.log(chalk_1.default[color](` ${severity}: ${count}`)); }); } catch (error) { spinner.fail('Scan failed!'); const err = error; console.error(chalk_1.default.red('Error:'), err.message || err); process.exit(1); } }); program .command('interactive') .alias('i') .description('Run SupaMend in interactive mode') .action(async () => { try { const interactive = new interactive_1.InteractiveCLI(); await interactive.run(); } catch (error) { console.error(chalk_1.default.red('Error:'), error); process.exit(1); } }); program .command('list') .description('List available scanners and reporters') .action(async () => { try { const supamend = new supamend_1.default(); const [scanners, reporters] = await Promise.all([ supamend.listScanners(), supamend.listReporters() ]); console.log(chalk_1.default.blue('\nAvailable Scanners:')); scanners.forEach((scanner) => { console.log(` - ${scanner}`); }); console.log(chalk_1.default.blue('\nAvailable Reporters:')); reporters.forEach((reporter) => { console.log(` - ${reporter}`); }); } catch (error) { console.error(chalk_1.default.red('Error:'), error); process.exit(1); } }); program.parse(); //# sourceMappingURL=cli.js.map