Skip to content
Merged
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
4 changes: 2 additions & 2 deletions __tests__/summary.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -747,8 +747,8 @@ test('addChangeVulnerabilitiesToSummary() - respects concurrency limit for API c
currentConcurrent++
maxConcurrent = Math.max(maxConcurrent, currentConcurrent)

// Simulate async API call with variable delay
await new Promise(resolve => setTimeout(resolve, Math.random() * 10))
// Simulate async API call with a small deterministic delay
await new Promise(resolve => setTimeout(resolve, 5))

currentConcurrent--

Expand Down
30 changes: 15 additions & 15 deletions dist/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

48 changes: 27 additions & 21 deletions src/summary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,21 +246,16 @@ function countScorecardWarnings(
* @param tasks - Array of functions that return promises
* @param limit - Maximum number of concurrent promises
*/
async function promisePool<T>(
tasks: (() => Promise<T>)[],
async function promisePool(
tasks: (() => Promise<void>)[],
limit: number
): Promise<T[]> {
const results: T[] = []
): Promise<void> {
const executing: Set<Promise<void>> = new Set()

for (let i = 0; i < tasks.length; i++) {
const task = tasks[i]
const index = i

// Wrap task execution to store result and clean up
for (const task of tasks) {
// Execute task and clean up
const wrappedPromise = (async () => {
const result = await task()
results[index] = result
await task()
})()

executing.add(wrappedPromise)
Expand All @@ -278,7 +273,6 @@ async function promisePool<T>(

// Wait for all remaining promises
await Promise.all(executing)
return results
}

export async function addChangeVulnerabilitiesToSummary(
Expand All @@ -301,9 +295,18 @@ export async function addChangeVulnerabilitiesToSummary(

// Query GitHub API for patch info with concurrency limiting
// Store all vulnerability entries (may be multiple per package with different ranges)
// Pre-normalize ecosystem, package name, and range to avoid repeated work in rendering
const patchInfo: Record<
string,
{eco: string; pkg: string; range: string; patch: string}[]
{
eco: string
pkg: string
range: string
patch: string
ecoLower: string
pkgLower: string
normalizedRange: string
}[]
> = {}
const apiClient = octokitClient()

Expand All @@ -326,11 +329,17 @@ export async function addChangeVulnerabilitiesToSummary(
const vulnRange = v.vulnerable_version_range || ''
const patchVerId = extractPatchVersionId(v.first_patched_version)
if (patchVerId) {
// Pre-normalize and cache values to avoid repeated work in rendering loop
const trimmedRange = vulnRange.trim()
const normalizedRange = trimmedRange.replace(/,\s*/g, ' ')
patchInfo[advId].push({
eco: normalizedEco,
pkg: pkgName,
range: vulnRange,
patch: patchVerId
patch: patchVerId,
ecoLower: normalizedEco, // Ecosystem already normalized to lowercase
pkgLower: pkgName.toLowerCase(),
normalizedRange
})
core.debug(
`Added patch info for ${pkgName} (${normalizedEco}): ${patchVerId} for range ${vulnRange}`
Expand Down Expand Up @@ -380,23 +389,20 @@ export async function addChangeVulnerabilitiesToSummary(
)

// Find matching entry by ecosystem, package name (case-insensitive), and version range
// Use pre-normalized values from cache to avoid repeated lowercasing and range conversion
let foundEntry:
| {eco: string; pkg: string; range: string; patch: string}
| undefined
for (const vulnEntry of advisoryEntries) {
if (vulnEntry.eco.toLowerCase() !== ecoLowercase) continue
if (vulnEntry.pkg.toLowerCase() !== packageLowercase) continue

// Normalize range once for both cache key and function call
const trimmedRangeOnce = vulnEntry.range.trim()
const normalizedRange = trimmedRangeOnce.replace(/,\s*/g, ' ')
if (vulnEntry.ecoLower !== ecoLowercase) continue
if (vulnEntry.pkgLower !== packageLowercase) continue

// Use fail-open (failClosed: false) for patch selection to avoid
// incorrectly matching on invalid ranges
// Use preTrimmed and preNormalized optimizations since we've done both
const isInRange = versionInRange(
normalizedChangeVersion,
normalizedRange,
vulnEntry.normalizedRange,
{preTrimmed: true, preNormalized: true, failClosed: false}
)

Expand Down