Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 31 additions & 8 deletions src/command/scan/output-scan-report.mts
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,24 @@ export function formatAlertTable(rows: ReportAlertRow[]): string[] {
row.introducedBy,
row.manifest,
])
const widths = headers.map((header, i) =>
Math.max(header.length, ...cells.map(cell => (cell[i] ?? '').length)),
)
const widths = headers.map(header => header.length)
for (
let rowIndex = 0, { length: rowCount } = cells;
rowIndex < rowCount;
rowIndex += 1
) {
const cell = cells[rowIndex]!
for (
let cellIndex = 0, { length: cellLength } = cell;
cellIndex < cellLength;
cellIndex += 1
) {
widths[cellIndex] = Math.max(
widths[cellIndex] ?? 0,
cell[cellIndex]?.length ?? 0,
)
}
}
const toRow = (values: string[]) =>
` ${values
.map((value, i) => value.padEnd(widths[i] ?? 0))
Expand Down Expand Up @@ -116,6 +131,12 @@ export function formatLabelledPairs(pairs: Array<[string, string]>): string[] {
)
}

export function isFullScanReport(
report: ScanReport | { healthy: boolean },
): report is ScanReport {
return 'alerts' in report
}

export async function outputGeneratedScanReport(
scanReport: Extract<ReturnType<typeof generateReport>, { ok: true }>,
config: Pick<
Expand Down Expand Up @@ -159,10 +180,10 @@ export async function outputGeneratedScanReport(

if (short) {
logger.log(scanReport.data.healthy ? 'OK' : 'ERR')
} else if (isFullScanReport(scanReport.data)) {
logger.log(toPlainTextReport(scanReport.data, includeLicensePolicy))
} else {
logger.log(
toPlainTextReport(scanReport.data as ScanReport, includeLicensePolicy),
)
logger.log(scanReport.data.healthy ? 'OK' : 'ERR')
}
}

Expand Down Expand Up @@ -401,8 +422,10 @@ export function toPlainTextReport(
lines.push(
` ${rows.length} ${pluralize('alert', { count: rows.length })} with a policy set to at least "${reportLevel}".`,
'',
...formatAlertTable(rows),
'',
)
for (const line of formatAlertTable(rows)) {
lines.push(line)
}
lines.push('')
return lines.join('\n')
}
28 changes: 27 additions & 1 deletion test/unit/command/scan/output-scan-report-text.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ import {
toPlainTextReport,
} from '../../../../src/command/scan/output-scan-report.mts'

import type { ScanReport } from '../../../../src/command/scan/report-generation.mts'
import type {
ReportLeafNode,
ScanReport,
} from '../../../../src/command/scan/report-generation.mts'

// Matches any ANSI escape sequence, the codes that show up as literal noise
// like "[32m" in a log viewer that does not interpret them.
Expand Down Expand Up @@ -204,6 +207,29 @@ describe('toPlainTextReport', () => {
expect(overlong).toEqual([])
})

it('renders a large alert report without argument-count failures', () => {
const alertRow: ReportLeafNode = {
manifest: ['package.json'],
policy: 'error',
type: 'envVars',
url: '',
}
const packages = new Map<string, Map<string, ReportLeafNode>>()
const alertCount = 200_000
for (let i = 0; i < alertCount; i += 1) {
packages.set(
`package-${String(i).padStart(6, '0')}`,
new Map([['1.0.0', alertRow]]),
)
}
const alerts: ScanReport['alerts'] = new Map([['npm', packages]])
const text = toPlainTextReport(buildReport({ alerts, healthy: false }))

expect(text).toContain('200000 alerts with a policy')
expect(text).toContain('package-000000')
expect(text).toContain('package-199999')
})

it('says so plainly when there are no alerts', () => {
const text = toPlainTextReport(
buildReport({ alerts: new Map(), healthy: true }),
Expand Down
Loading