mirror of
https://github.com/samiyev/puaros.git
synced 2025-12-27 23:06:54 +05:00
feat: add --limit CLI option for output control
- Add --limit/-l option to limit detailed violation output - Implement limit logic in displayGroupedViolations function - Show warning when violations exceed limit - Works with severity filters (--only-critical, --min-severity) - Extract severity labels and headers to constants - Improve CLI maintainability with SEVERITY_DISPLAY_LABELS and SEVERITY_SECTION_HEADERS
This commit is contained in:
@@ -22,6 +22,7 @@ export const CLI_DESCRIPTIONS = {
|
||||
NO_ARCHITECTURE_OPTION: "Skip architecture checks",
|
||||
MIN_SEVERITY_OPTION: "Minimum severity level (critical, high, medium, low)",
|
||||
ONLY_CRITICAL_OPTION: "Show only critical severity issues",
|
||||
LIMIT_OPTION: "Limit detailed output to specified number of violations per category",
|
||||
} as const
|
||||
|
||||
export const CLI_OPTIONS = {
|
||||
@@ -31,6 +32,21 @@ export const CLI_OPTIONS = {
|
||||
NO_ARCHITECTURE: "--no-architecture",
|
||||
MIN_SEVERITY: "--min-severity <level>",
|
||||
ONLY_CRITICAL: "--only-critical",
|
||||
LIMIT: "-l, --limit <number>",
|
||||
} as const
|
||||
|
||||
export const SEVERITY_DISPLAY_LABELS = {
|
||||
CRITICAL: "🔴 CRITICAL",
|
||||
HIGH: "🟠 HIGH",
|
||||
MEDIUM: "🟡 MEDIUM",
|
||||
LOW: "🟢 LOW",
|
||||
} as const
|
||||
|
||||
export const SEVERITY_SECTION_HEADERS = {
|
||||
CRITICAL: "\n═══════════════════════════════════════════\n🔴 CRITICAL SEVERITY\n═══════════════════════════════════════════",
|
||||
HIGH: "\n═══════════════════════════════════════════\n🟠 HIGH SEVERITY\n═══════════════════════════════════════════",
|
||||
MEDIUM: "\n═══════════════════════════════════════════\n🟡 MEDIUM SEVERITY\n═══════════════════════════════════════════",
|
||||
LOW: "\n═══════════════════════════════════════════\n🟢 LOW SEVERITY\n═══════════════════════════════════════════",
|
||||
} as const
|
||||
|
||||
export const CLI_ARGUMENTS = {
|
||||
|
||||
@@ -10,25 +10,23 @@ import {
|
||||
CLI_MESSAGES,
|
||||
CLI_OPTIONS,
|
||||
DEFAULT_EXCLUDES,
|
||||
SEVERITY_DISPLAY_LABELS,
|
||||
SEVERITY_SECTION_HEADERS,
|
||||
} from "./constants"
|
||||
import { SEVERITY_LEVELS, SEVERITY_ORDER, type SeverityLevel } from "../shared/constants"
|
||||
|
||||
const SEVERITY_LABELS: Record<SeverityLevel, string> = {
|
||||
[SEVERITY_LEVELS.CRITICAL]: "🔴 CRITICAL",
|
||||
[SEVERITY_LEVELS.HIGH]: "🟠 HIGH",
|
||||
[SEVERITY_LEVELS.MEDIUM]: "🟡 MEDIUM",
|
||||
[SEVERITY_LEVELS.LOW]: "🟢 LOW",
|
||||
[SEVERITY_LEVELS.CRITICAL]: SEVERITY_DISPLAY_LABELS.CRITICAL,
|
||||
[SEVERITY_LEVELS.HIGH]: SEVERITY_DISPLAY_LABELS.HIGH,
|
||||
[SEVERITY_LEVELS.MEDIUM]: SEVERITY_DISPLAY_LABELS.MEDIUM,
|
||||
[SEVERITY_LEVELS.LOW]: SEVERITY_DISPLAY_LABELS.LOW,
|
||||
}
|
||||
|
||||
const SEVERITY_HEADER: Record<SeverityLevel, string> = {
|
||||
[SEVERITY_LEVELS.CRITICAL]:
|
||||
"\n═══════════════════════════════════════════\n🔴 CRITICAL SEVERITY\n═══════════════════════════════════════════",
|
||||
[SEVERITY_LEVELS.HIGH]:
|
||||
"\n═══════════════════════════════════════════\n🟠 HIGH SEVERITY\n═══════════════════════════════════════════",
|
||||
[SEVERITY_LEVELS.MEDIUM]:
|
||||
"\n═══════════════════════════════════════════\n🟡 MEDIUM SEVERITY\n═══════════════════════════════════════════",
|
||||
[SEVERITY_LEVELS.LOW]:
|
||||
"\n═══════════════════════════════════════════\n🟢 LOW SEVERITY\n═══════════════════════════════════════════",
|
||||
[SEVERITY_LEVELS.CRITICAL]: SEVERITY_SECTION_HEADERS.CRITICAL,
|
||||
[SEVERITY_LEVELS.HIGH]: SEVERITY_SECTION_HEADERS.HIGH,
|
||||
[SEVERITY_LEVELS.MEDIUM]: SEVERITY_SECTION_HEADERS.MEDIUM,
|
||||
[SEVERITY_LEVELS.LOW]: SEVERITY_SECTION_HEADERS.LOW,
|
||||
}
|
||||
|
||||
function groupBySeverity<T extends { severity: SeverityLevel }>(
|
||||
@@ -37,7 +35,7 @@ function groupBySeverity<T extends { severity: SeverityLevel }>(
|
||||
const grouped = new Map<SeverityLevel, T[]>()
|
||||
|
||||
for (const violation of violations) {
|
||||
const existing = grouped.get(violation.severity) || []
|
||||
const existing = grouped.get(violation.severity) ?? []
|
||||
existing.push(violation)
|
||||
grouped.set(violation.severity, existing)
|
||||
}
|
||||
@@ -60,6 +58,7 @@ function filterBySeverity<T extends { severity: SeverityLevel }>(
|
||||
function displayGroupedViolations<T extends { severity: SeverityLevel }>(
|
||||
violations: T[],
|
||||
displayFn: (v: T, index: number) => void,
|
||||
limit?: number,
|
||||
): void {
|
||||
const grouped = groupBySeverity(violations)
|
||||
const severities: SeverityLevel[] = [
|
||||
@@ -69,16 +68,35 @@ function displayGroupedViolations<T extends { severity: SeverityLevel }>(
|
||||
SEVERITY_LEVELS.LOW,
|
||||
]
|
||||
|
||||
let totalDisplayed = 0
|
||||
const totalAvailable = violations.length
|
||||
|
||||
for (const severity of severities) {
|
||||
const items = grouped.get(severity)
|
||||
if (items && items.length > 0) {
|
||||
console.log(SEVERITY_HEADER[severity])
|
||||
console.log(`Found ${String(items.length)} issue(s)\n`)
|
||||
items.forEach(displayFn)
|
||||
console.warn(SEVERITY_HEADER[severity])
|
||||
console.warn(`Found ${String(items.length)} issue(s)\n`)
|
||||
|
||||
const itemsToDisplay =
|
||||
limit !== undefined ? items.slice(0, limit - totalDisplayed) : items
|
||||
itemsToDisplay.forEach((item, index) => {
|
||||
displayFn(item, totalDisplayed + index)
|
||||
})
|
||||
totalDisplayed += itemsToDisplay.length
|
||||
|
||||
if (limit !== undefined && totalDisplayed >= limit) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (limit !== undefined && totalAvailable > limit) {
|
||||
console.warn(
|
||||
`\n⚠️ Showing first ${String(limit)} of ${String(totalAvailable)} issues (use --limit to adjust)\n`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const program = new Command()
|
||||
|
||||
program.name(CLI_COMMANDS.NAME).description(CLI_DESCRIPTIONS.MAIN).version(version)
|
||||
@@ -93,6 +111,7 @@ program
|
||||
.option(CLI_OPTIONS.NO_ARCHITECTURE, CLI_DESCRIPTIONS.NO_ARCHITECTURE_OPTION)
|
||||
.option(CLI_OPTIONS.MIN_SEVERITY, CLI_DESCRIPTIONS.MIN_SEVERITY_OPTION)
|
||||
.option(CLI_OPTIONS.ONLY_CRITICAL, CLI_DESCRIPTIONS.ONLY_CRITICAL_OPTION, false)
|
||||
.option(CLI_OPTIONS.LIMIT, CLI_DESCRIPTIONS.LIMIT_OPTION)
|
||||
.action(async (path: string, options) => {
|
||||
try {
|
||||
console.log(CLI_MESSAGES.ANALYZING)
|
||||
@@ -102,6 +121,7 @@ program
|
||||
exclude: options.exclude,
|
||||
})
|
||||
|
||||
const { metrics } = result
|
||||
let {
|
||||
hardcodeViolations,
|
||||
violations,
|
||||
@@ -111,7 +131,6 @@ program
|
||||
entityExposureViolations,
|
||||
dependencyDirectionViolations,
|
||||
repositoryPatternViolations,
|
||||
metrics,
|
||||
} = result
|
||||
|
||||
const minSeverity: SeverityLevel | undefined = options.onlyCritical
|
||||
@@ -120,6 +139,10 @@ program
|
||||
? (options.minSeverity.toLowerCase() as SeverityLevel)
|
||||
: undefined
|
||||
|
||||
const limit: number | undefined = options.limit
|
||||
? parseInt(options.limit, 10)
|
||||
: undefined
|
||||
|
||||
if (minSeverity) {
|
||||
violations = filterBySeverity(violations, minSeverity)
|
||||
hardcodeViolations = filterBySeverity(hardcodeViolations, minSeverity)
|
||||
@@ -167,13 +190,17 @@ program
|
||||
`\n${CLI_MESSAGES.VIOLATIONS_HEADER} ${String(violations.length)} ${CLI_LABELS.ARCHITECTURE_VIOLATIONS}`,
|
||||
)
|
||||
|
||||
displayGroupedViolations(violations, (v, index) => {
|
||||
displayGroupedViolations(
|
||||
violations,
|
||||
(v, index) => {
|
||||
console.log(`${String(index + 1)}. ${v.file}`)
|
||||
console.log(` Severity: ${SEVERITY_LABELS[v.severity]}`)
|
||||
console.log(` Rule: ${v.rule}`)
|
||||
console.log(` ${v.message}`)
|
||||
console.log("")
|
||||
})
|
||||
},
|
||||
limit,
|
||||
)
|
||||
}
|
||||
|
||||
// Circular dependency violations
|
||||
@@ -182,7 +209,9 @@ program
|
||||
`\n${CLI_MESSAGES.CIRCULAR_DEPS_HEADER} ${String(circularDependencyViolations.length)} ${CLI_LABELS.CIRCULAR_DEPENDENCIES}`,
|
||||
)
|
||||
|
||||
displayGroupedViolations(circularDependencyViolations, (cd, index) => {
|
||||
displayGroupedViolations(
|
||||
circularDependencyViolations,
|
||||
(cd, index) => {
|
||||
console.log(`${String(index + 1)}. ${cd.message}`)
|
||||
console.log(` Severity: ${SEVERITY_LABELS[cd.severity]}`)
|
||||
console.log(" Cycle path:")
|
||||
@@ -193,7 +222,9 @@ program
|
||||
` ${String(cd.cycle.length + 1)}. ${cd.cycle[0]} (back to start)`,
|
||||
)
|
||||
console.log("")
|
||||
})
|
||||
},
|
||||
limit,
|
||||
)
|
||||
}
|
||||
|
||||
// Naming convention violations
|
||||
@@ -202,7 +233,9 @@ program
|
||||
`\n${CLI_MESSAGES.NAMING_VIOLATIONS_HEADER} ${String(namingViolations.length)} ${CLI_LABELS.NAMING_VIOLATIONS}`,
|
||||
)
|
||||
|
||||
displayGroupedViolations(namingViolations, (nc, index) => {
|
||||
displayGroupedViolations(
|
||||
namingViolations,
|
||||
(nc, index) => {
|
||||
console.log(`${String(index + 1)}. ${nc.file}`)
|
||||
console.log(` Severity: ${SEVERITY_LABELS[nc.severity]}`)
|
||||
console.log(` File: ${nc.fileName}`)
|
||||
@@ -213,7 +246,9 @@ program
|
||||
console.log(` 💡 Suggestion: ${nc.suggestion}`)
|
||||
}
|
||||
console.log("")
|
||||
})
|
||||
},
|
||||
limit,
|
||||
)
|
||||
}
|
||||
|
||||
// Framework leak violations
|
||||
@@ -222,7 +257,9 @@ program
|
||||
`\n🏗️ Found ${String(frameworkLeakViolations.length)} framework leak(s)`,
|
||||
)
|
||||
|
||||
displayGroupedViolations(frameworkLeakViolations, (fl, index) => {
|
||||
displayGroupedViolations(
|
||||
frameworkLeakViolations,
|
||||
(fl, index) => {
|
||||
console.log(`${String(index + 1)}. ${fl.file}`)
|
||||
console.log(` Severity: ${SEVERITY_LABELS[fl.severity]}`)
|
||||
console.log(` Package: ${fl.packageName}`)
|
||||
@@ -232,7 +269,9 @@ program
|
||||
console.log(` ${fl.message}`)
|
||||
console.log(` 💡 Suggestion: ${fl.suggestion}`)
|
||||
console.log("")
|
||||
})
|
||||
},
|
||||
limit,
|
||||
)
|
||||
}
|
||||
|
||||
// Entity exposure violations
|
||||
@@ -241,7 +280,9 @@ program
|
||||
`\n🎭 Found ${String(entityExposureViolations.length)} entity exposure(s)`,
|
||||
)
|
||||
|
||||
displayGroupedViolations(entityExposureViolations, (ee, index) => {
|
||||
displayGroupedViolations(
|
||||
entityExposureViolations,
|
||||
(ee, index) => {
|
||||
const location = ee.line ? `${ee.file}:${String(ee.line)}` : ee.file
|
||||
console.log(`${String(index + 1)}. ${location}`)
|
||||
console.log(` Severity: ${SEVERITY_LABELS[ee.severity]}`)
|
||||
@@ -260,7 +301,9 @@ program
|
||||
}
|
||||
})
|
||||
console.log("")
|
||||
})
|
||||
},
|
||||
limit,
|
||||
)
|
||||
}
|
||||
|
||||
// Dependency direction violations
|
||||
@@ -269,7 +312,9 @@ program
|
||||
`\n⚠️ Found ${String(dependencyDirectionViolations.length)} dependency direction violation(s)`,
|
||||
)
|
||||
|
||||
displayGroupedViolations(dependencyDirectionViolations, (dd, index) => {
|
||||
displayGroupedViolations(
|
||||
dependencyDirectionViolations,
|
||||
(dd, index) => {
|
||||
console.log(`${String(index + 1)}. ${dd.file}`)
|
||||
console.log(` Severity: ${SEVERITY_LABELS[dd.severity]}`)
|
||||
console.log(` From Layer: ${dd.fromLayer}`)
|
||||
@@ -278,7 +323,9 @@ program
|
||||
console.log(` ${dd.message}`)
|
||||
console.log(` 💡 Suggestion: ${dd.suggestion}`)
|
||||
console.log("")
|
||||
})
|
||||
},
|
||||
limit,
|
||||
)
|
||||
}
|
||||
|
||||
// Repository pattern violations
|
||||
@@ -287,7 +334,9 @@ program
|
||||
`\n📦 Found ${String(repositoryPatternViolations.length)} repository pattern violation(s)`,
|
||||
)
|
||||
|
||||
displayGroupedViolations(repositoryPatternViolations, (rp, index) => {
|
||||
displayGroupedViolations(
|
||||
repositoryPatternViolations,
|
||||
(rp, index) => {
|
||||
console.log(`${String(index + 1)}. ${rp.file}`)
|
||||
console.log(` Severity: ${SEVERITY_LABELS[rp.severity]}`)
|
||||
console.log(` Layer: ${rp.layer}`)
|
||||
@@ -296,7 +345,9 @@ program
|
||||
console.log(` ${rp.message}`)
|
||||
console.log(` 💡 Suggestion: ${rp.suggestion}`)
|
||||
console.log("")
|
||||
})
|
||||
},
|
||||
limit,
|
||||
)
|
||||
}
|
||||
|
||||
// Hardcode violations
|
||||
@@ -305,7 +356,9 @@ program
|
||||
`\n${CLI_MESSAGES.HARDCODE_VIOLATIONS_HEADER} ${String(hardcodeViolations.length)} ${CLI_LABELS.HARDCODE_VIOLATIONS}`,
|
||||
)
|
||||
|
||||
displayGroupedViolations(hardcodeViolations, (hc, index) => {
|
||||
displayGroupedViolations(
|
||||
hardcodeViolations,
|
||||
(hc, index) => {
|
||||
console.log(
|
||||
`${String(index + 1)}. ${hc.file}:${String(hc.line)}:${String(hc.column)}`,
|
||||
)
|
||||
@@ -316,7 +369,9 @@ program
|
||||
console.log(` 💡 Suggested: ${hc.suggestion.constantName}`)
|
||||
console.log(` 📁 Location: ${hc.suggestion.location}`)
|
||||
console.log("")
|
||||
})
|
||||
},
|
||||
limit,
|
||||
)
|
||||
}
|
||||
|
||||
// Summary
|
||||
|
||||
Reference in New Issue
Block a user