Address review feedback: deterministic tests, cached normalization, simplified promisePool (#9)

* Initial plan

* Apply PR review comments: deterministic delays, cached normalization, simplified promisePool

Co-authored-by: felickz <1760475+felickz@users.noreply.github.com>

* Improve comment clarity for ecoLower field

Co-authored-by: felickz <1760475+felickz@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: felickz <1760475+felickz@users.noreply.github.com>
This commit is contained in:
Copilot
2026-02-18 06:33:39 -05:00
committed by GitHub
parent 539c79be65
commit a6c34d8785
4 changed files with 45 additions and 39 deletions
+2 -2
View File
@@ -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--
Generated Vendored
+15 -15
View File
@@ -1820,15 +1820,11 @@ function countScorecardWarnings(scorecard, config) {
*/
function promisePool(tasks, limit) {
return __awaiter(this, void 0, void 0, function* () {
const results = [];
const executing = 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 = (() => __awaiter(this, void 0, void 0, function* () {
const result = yield task();
results[index] = result;
yield task();
}))();
executing.add(wrappedPromise);
// When promise completes, remove it from the executing set
@@ -1842,7 +1838,6 @@ function promisePool(tasks, limit) {
}
// Wait for all remaining promises
yield Promise.all(executing);
return results;
});
}
function addChangeVulnerabilitiesToSummary(vulnerableChanges, severity) {
@@ -1860,6 +1855,7 @@ function addChangeVulnerabilitiesToSummary(vulnerableChanges, severity) {
}
// 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 = {};
const apiClient = (0, utils_1.octokitClient)();
// Create tasks for promise pool
@@ -1879,11 +1875,17 @@ function addChangeVulnerabilitiesToSummary(vulnerableChanges, severity) {
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}`);
}
@@ -1920,19 +1922,17 @@ function addChangeVulnerabilitiesToSummary(vulnerableChanges, severity) {
const normalizedChangeVersion = change.version.trim();
core.debug(`Looking up patch for ${change.name}@${change.version} (${ecoLowercase}) in ${vuln.advisory_ghsa_id}`);
// 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;
for (const vulnEntry of advisoryEntries) {
if (vulnEntry.eco.toLowerCase() !== ecoLowercase)
if (vulnEntry.ecoLower !== ecoLowercase)
continue;
if (vulnEntry.pkg.toLowerCase() !== packageLowercase)
if (vulnEntry.pkgLower !== packageLowercase)
continue;
// Normalize range once for both cache key and function call
const trimmedRangeOnce = vulnEntry.range.trim();
const normalizedRange = trimmedRangeOnce.replace(/,\s*/g, ' ');
// 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, { preTrimmed: true, preNormalized: true, failClosed: false });
const isInRange = versionInRange(normalizedChangeVersion, vulnEntry.normalizedRange, { preTrimmed: true, preNormalized: true, failClosed: false });
if (isInRange) {
foundEntry = vulnEntry;
break;
Generated Vendored
+1 -1
View File
File diff suppressed because one or more lines are too long
+27 -21
View File
@@ -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)
@@ -278,7 +273,6 @@ async function promisePool<T>(
// Wait for all remaining promises
await Promise.all(executing)
return results
}
export async function addChangeVulnerabilitiesToSummary(
@@ -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()
@@ -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}`
@@ -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}
)