UNPKG

@syntropysoft/praetorian

Version:

Praetorian CLI – A universal multi-environment configuration validator for DevSecOps teams. Validate, compare, and secure YAML/ENV files with ease.

226 lines • 9.16 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const core_1 = require("@oclif/core"); const chalk_1 = __importDefault(require("chalk")); const ConfigParser_1 = require("../infrastructure/parsers/ConfigParser"); const EqualityRule_1 = require("../domain/rules/EqualityRule"); const FileReaderService_1 = require("../infrastructure/adapters/FileReaderService"); class EmptyKeys extends core_1.Command { async run() { const { args, flags } = await this.parse(EmptyKeys); try { const filesToAnalyze = this.determineFilesToAnalyze(args, flags); const configFiles = await this.loadFiles(filesToAnalyze); const result = await new EqualityRule_1.EqualityRule().execute(configFiles); this.displayEmptyKeysReport(result, flags); } catch (error) { this.error(error instanceof Error ? error.message : 'Unknown error'); this.exit(1); } } determineFilesToAnalyze(args, flags) { if (args.files?.length > 0) { return Array.isArray(args.files) ? args.files : [args.files]; } const configParser = new ConfigParser_1.ConfigParser(flags.config); if (!configParser.exists()) { this.error(`Configuration file not found: ${flags.config}`); this.log(chalk_1.default.yellow('\nCreate a configuration file with:')); this.log(chalk_1.default.gray('praetorian init')); throw new Error('Configuration file not found'); } return flags.env ? configParser.getEnvironmentFiles(flags.env) : configParser.getFilesToCompare(); } async loadFiles(filePaths) { const fileReaderService = new FileReaderService_1.FileReaderService(); const { valid, invalid } = fileReaderService.validateFiles(filePaths); if (invalid.length > 0) { const supportedExtensions = fileReaderService.getSupportedExtensions().join(', '); throw new Error(`Unsupported file formats: ${invalid.join(', ')}. ` + `Supported extensions: ${supportedExtensions}`); } return await fileReaderService.readFiles(valid); } displayEmptyKeysReport(result, flags) { const emptyKeys = result.info || []; const totalFiles = result.metadata?.filesCompared || 0; const outputHandlers = { json: () => this.displayJsonReport(emptyKeys, totalFiles, result.metadata), csv: () => this.displayCsvReport(emptyKeys, flags), pretty: () => this.displayPrettyReport(emptyKeys, totalFiles, flags) }; const handler = outputHandlers[flags.output] || outputHandlers.pretty; handler(); } displayJsonReport(emptyKeys, totalFiles, metadata) { const report = { summary: { totalFiles, totalEmptyKeys: emptyKeys.length, filesWithEmptyKeys: this.getUniqueFiles(emptyKeys).length, }, emptyKeys: this.transformToEmptyKeyInfo(emptyKeys), metadata, }; console.log(JSON.stringify(report, null, 2)); } displayCsvReport(emptyKeys, flags) { const headers = ['Key', 'File', 'Value Type', 'Value']; const rows = this.transformToCsvRows(emptyKeys, flags); console.log(headers.join(',')); rows.forEach(row => console.log(row.join(','))); } displayPrettyReport(emptyKeys, totalFiles, flags) { console.log(chalk_1.default.blue('\nšŸ” Empty Keys Report:\n')); if (emptyKeys.length === 0) { console.log(chalk_1.default.green('āœ… No empty keys found!')); console.log(chalk_1.default.gray(`Analyzed ${totalFiles} file(s)`)); return; } this.displaySummary(emptyKeys, totalFiles); this.displayEmptyKeysList(emptyKeys, flags); this.displayRecommendations(); } displaySummary(emptyKeys, totalFiles) { const summaryLines = [ ` • Files analyzed: ${totalFiles}`, ` • Total empty keys: ${emptyKeys.length}`, ` • Files with empty keys: ${this.getUniqueFiles(emptyKeys).length}` ]; console.log(chalk_1.default.blue('šŸ“Š Summary:')); summaryLines.forEach(line => console.log(line)); } displayEmptyKeysList(emptyKeys, flags) { if (flags['group-by-file']) { this.displayGroupedByFile(emptyKeys, flags); } else { this.displayFlatList(emptyKeys, flags); } } displayGroupedByFile(emptyKeys, flags) { const grouped = this.groupByFile(emptyKeys); Object.entries(grouped) .map(([filePath, keys]) => ({ filePath, keys, count: keys.length })) .forEach(({ filePath, keys, count }) => { console.log(chalk_1.default.cyan(`\nšŸ“ ${filePath} (${count} empty keys):`)); this.renderEmptyKeys(keys, flags, ' '); }); } displayFlatList(emptyKeys, flags) { console.log(chalk_1.default.blue('\nšŸ“‹ Empty Keys List:')); this.renderEmptyKeys(emptyKeys, flags, ' '); } renderEmptyKeys(emptyKeys, flags, prefix) { emptyKeys .map(key => this.formatEmptyKeyDisplay(key, flags, prefix)) .forEach(line => console.log(line)); } formatEmptyKeyDisplay(key, flags, prefix) { const keyDisplay = chalk_1.default.blue(`${prefix}• ${key.path}`); const fileDisplay = chalk_1.default.gray(` [${key.context?.file}]`); const valueDisplay = flags['include-values'] ? chalk_1.default.gray(` (${key.context?.valueType}: ${JSON.stringify(key.context?.value)})`) : ''; return `${keyDisplay}${fileDisplay}${valueDisplay}`; } displayRecommendations() { const recommendations = [ ' • Review empty keys to ensure they are intentional', ' • Consider using environment-specific values for empty keys', ' • Add empty keys to ignore list if they are expected', ' • Use --include-values to see actual empty values' ]; console.log(chalk_1.default.yellow('\nšŸ’” Recommendations:')); recommendations.forEach(rec => console.log(chalk_1.default.gray(rec))); } // Pure functions for data transformation transformToEmptyKeyInfo(emptyKeys) { return emptyKeys.map(key => ({ path: key.path, file: key.context?.file || '', value: key.context?.value, valueType: key.context?.valueType || '', message: key.message || '' })); } transformToCsvRows(emptyKeys, flags) { return emptyKeys.map(key => [ `"${key.path}"`, `"${key.context?.file || ''}"`, `"${key.context?.valueType || ''}"`, `"${flags['include-values'] ? JSON.stringify(key.context?.value || '') : ''}"`, ]); } groupByFile(emptyKeys) { return emptyKeys.reduce((acc, key) => { const filePath = key.context?.file || 'unknown'; return { ...acc, [filePath]: [...(acc[filePath] || []), key] }; }, {}); } getUniqueFiles(emptyKeys) { return emptyKeys .map(key => key.context?.file) .filter(Boolean) .reduce((unique, file) => unique.includes(file) ? unique : [...unique, file], []); } } EmptyKeys.description = 'Generate a report of empty keys in configuration files'; EmptyKeys.examples = [ '$ praetorian empty-keys', '$ praetorian empty-keys --env dev', '$ praetorian empty-keys config-dev.yaml config-prod.yaml', '$ praetorian empty-keys --output json', '$ praetorian empty-keys --include-values', ]; EmptyKeys.flags = { env: core_1.Flags.string({ char: 'e', description: 'Environment to analyze (dev, staging, prod)', required: false, }), output: core_1.Flags.string({ char: 'o', description: 'Output format (pretty, json, csv)', options: ['pretty', 'json', 'csv'], default: 'pretty', }), config: core_1.Flags.string({ char: 'c', description: 'Path to praetorian.yaml configuration file', default: 'praetorian.yaml', }), 'include-values': core_1.Flags.boolean({ char: 'v', description: 'Include empty values in the report', default: false, }), 'group-by-file': core_1.Flags.boolean({ char: 'g', description: 'Group empty keys by file', default: false, }), help: core_1.Flags.help({ char: 'h' }), }; EmptyKeys.args = { files: core_1.Args.string({ description: 'Configuration files to analyze', required: false, multiple: true, }), }; exports.default = EmptyKeys; //# sourceMappingURL=empty-keys.js.map