supamend
Version:
Pluggable DevSecOps Security Scanner with 10+ scanners and multiple reporting channels
188 lines • 8.29 kB
JavaScript
;
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