Implement review feedback: concurrency limiting, semver coercion, logging improvements, and test coverage (#8)
* Initial plan * Implement PR review comments: concurrency limiting, semver coerce, improved logging, test fixes Co-authored-by: felickz <[email protected]> * Fix promise pool race condition and remove .then() usage Co-authored-by: felickz <[email protected]> * Add tests for semver coercion and promise pool concurrency, simplify Map to Set Co-authored-by: felickz <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: felickz <[email protected]>
This commit is contained in:
+182
-1
@@ -19,7 +19,7 @@ beforeEach(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
jest.clearAllMocks()
|
jest.restoreAllMocks()
|
||||||
core.summary.emptyBuffer()
|
core.summary.emptyBuffer()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -639,3 +639,184 @@ test('addChangeVulnerabilitiesToSummary() - handles RestSharp GHSA-4rr6-2v9v-wcp
|
|||||||
ghsa_id: 'GHSA-4rr6-2v9v-wcpc'
|
ghsa_id: 'GHSA-4rr6-2v9v-wcpc'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('addChangeVulnerabilitiesToSummary() - handles version coercion for non-strict semver versions', async () => {
|
||||||
|
// Test that versions like "8.0" (without patch version) can be coerced to "8.0.0"
|
||||||
|
// for successful range matching in fail-open mode (patch selection)
|
||||||
|
const pkg = createTestChange({
|
||||||
|
ecosystem: 'npm',
|
||||||
|
name: 'test-package',
|
||||||
|
version: '8.0', // Non-strict semver version
|
||||||
|
vulnerabilities: [
|
||||||
|
createTestVulnerability({
|
||||||
|
advisory_ghsa_id: 'GHSA-test-1234',
|
||||||
|
advisory_summary: 'Test vulnerability',
|
||||||
|
severity: 'high'
|
||||||
|
})
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
mockOctokitRequest.mockResolvedValueOnce({
|
||||||
|
data: {
|
||||||
|
vulnerabilities: [
|
||||||
|
{
|
||||||
|
package: {
|
||||||
|
ecosystem: 'npm',
|
||||||
|
name: 'test-package'
|
||||||
|
},
|
||||||
|
vulnerable_version_range: '>= 8.0.0, < 9.0.0',
|
||||||
|
first_patched_version: '9.0.0'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const changes = [pkg]
|
||||||
|
await summary.addChangeVulnerabilitiesToSummary(changes, 'low')
|
||||||
|
|
||||||
|
const text = core.summary.stringify()
|
||||||
|
|
||||||
|
// Should coerce "8.0" to "8.0.0" and successfully match the range,
|
||||||
|
// showing the patched version instead of N/A
|
||||||
|
expect(text).toContain('9.0.0')
|
||||||
|
expect(text).not.toContain('N/A')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('addChangeVulnerabilitiesToSummary() - handles invalid versions in fail-open mode', async () => {
|
||||||
|
// Test that completely invalid versions that can't be coerced
|
||||||
|
// still return N/A gracefully in fail-open mode
|
||||||
|
const pkg = createTestChange({
|
||||||
|
ecosystem: 'npm',
|
||||||
|
name: 'test-package',
|
||||||
|
version: 'invalid-version-string',
|
||||||
|
vulnerabilities: [
|
||||||
|
createTestVulnerability({
|
||||||
|
advisory_ghsa_id: 'GHSA-test-5678',
|
||||||
|
advisory_summary: 'Test vulnerability',
|
||||||
|
severity: 'high'
|
||||||
|
})
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
mockOctokitRequest.mockResolvedValueOnce({
|
||||||
|
data: {
|
||||||
|
vulnerabilities: [
|
||||||
|
{
|
||||||
|
package: {
|
||||||
|
ecosystem: 'npm',
|
||||||
|
name: 'test-package'
|
||||||
|
},
|
||||||
|
vulnerable_version_range: '>= 1.0.0, < 2.0.0',
|
||||||
|
first_patched_version: '2.0.0'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const changes = [pkg]
|
||||||
|
await summary.addChangeVulnerabilitiesToSummary(changes, 'low')
|
||||||
|
|
||||||
|
const text = core.summary.stringify()
|
||||||
|
|
||||||
|
// Should show N/A since version can't be coerced or matched
|
||||||
|
expect(text).toContain('N/A')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('addChangeVulnerabilitiesToSummary() - respects concurrency limit for API calls', async () => {
|
||||||
|
// Create 15 packages with different vulnerabilities to test concurrency limiting
|
||||||
|
const packages = Array.from({length: 15}, (_, i) =>
|
||||||
|
createTestChange({
|
||||||
|
ecosystem: 'npm',
|
||||||
|
name: `package-${i}`,
|
||||||
|
version: '1.0.0',
|
||||||
|
vulnerabilities: [
|
||||||
|
createTestVulnerability({
|
||||||
|
advisory_ghsa_id: `GHSA-test-${i.toString().padStart(4, '0')}`,
|
||||||
|
advisory_summary: `Vulnerability ${i}`,
|
||||||
|
severity: 'high'
|
||||||
|
})
|
||||||
|
]
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
// Track concurrent calls
|
||||||
|
let maxConcurrent = 0
|
||||||
|
let currentConcurrent = 0
|
||||||
|
|
||||||
|
mockOctokitRequest.mockImplementation(async () => {
|
||||||
|
currentConcurrent++
|
||||||
|
maxConcurrent = Math.max(maxConcurrent, currentConcurrent)
|
||||||
|
|
||||||
|
// Simulate async API call with variable delay
|
||||||
|
await new Promise(resolve => setTimeout(resolve, Math.random() * 10))
|
||||||
|
|
||||||
|
currentConcurrent--
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
vulnerabilities: [
|
||||||
|
{
|
||||||
|
package: {ecosystem: 'npm', name: 'test'},
|
||||||
|
vulnerable_version_range: '>= 1.0.0, < 2.0.0',
|
||||||
|
first_patched_version: '2.0.0'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await summary.addChangeVulnerabilitiesToSummary(packages, 'low')
|
||||||
|
|
||||||
|
// Verify that concurrency limit (10) was respected
|
||||||
|
expect(maxConcurrent).toBeLessThanOrEqual(10)
|
||||||
|
// Verify all 15 unique advisories were fetched
|
||||||
|
expect(mockOctokitRequest).toHaveBeenCalledTimes(15)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('addChangeVulnerabilitiesToSummary() - completes all tasks even with varying durations', async () => {
|
||||||
|
// Test that promise pool doesn't lose tasks when some complete faster than others
|
||||||
|
const packages = Array.from({length: 20}, (_, i) =>
|
||||||
|
createTestChange({
|
||||||
|
ecosystem: 'npm',
|
||||||
|
name: `package-${i}`,
|
||||||
|
version: '1.0.0',
|
||||||
|
vulnerabilities: [
|
||||||
|
createTestVulnerability({
|
||||||
|
advisory_ghsa_id: `GHSA-vary-${i.toString().padStart(4, '0')}`,
|
||||||
|
advisory_summary: `Vulnerability ${i}`,
|
||||||
|
severity: 'high'
|
||||||
|
})
|
||||||
|
]
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const completedAdvisories = new Set<string>()
|
||||||
|
|
||||||
|
mockOctokitRequest.mockImplementation(
|
||||||
|
async (path: string, params: {ghsa_id: string}) => {
|
||||||
|
// Variable delay to simulate real-world API response times
|
||||||
|
const delay = Math.random() * 50
|
||||||
|
await new Promise(resolve => setTimeout(resolve, delay))
|
||||||
|
|
||||||
|
completedAdvisories.add(params.ghsa_id)
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
vulnerabilities: [
|
||||||
|
{
|
||||||
|
package: {ecosystem: 'npm', name: 'test'},
|
||||||
|
vulnerable_version_range: '>= 1.0.0, < 2.0.0',
|
||||||
|
first_patched_version: '2.0.0'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
await summary.addChangeVulnerabilitiesToSummary(packages, 'low')
|
||||||
|
|
||||||
|
// Verify all 20 unique advisories were fetched and completed
|
||||||
|
expect(completedAdvisories.size).toBe(20)
|
||||||
|
expect(mockOctokitRequest).toHaveBeenCalledTimes(20)
|
||||||
|
})
|
||||||
|
|||||||
+54
-5
@@ -1654,6 +1654,7 @@ const icons = {
|
|||||||
warning: '⚠️'
|
warning: '⚠️'
|
||||||
};
|
};
|
||||||
const MAX_SCANNED_FILES_BYTES = 1048576;
|
const MAX_SCANNED_FILES_BYTES = 1048576;
|
||||||
|
const API_CONCURRENCY_LIMIT = 10; // Limit concurrent API requests to avoid rate limiting
|
||||||
/**
|
/**
|
||||||
* Helper to check if a version falls within a vulnerable range.
|
* Helper to check if a version falls within a vulnerable range.
|
||||||
* Uses the `semver` library for proper prerelease handling and range parsing.
|
* Uses the `semver` library for proper prerelease handling and range parsing.
|
||||||
@@ -1691,8 +1692,17 @@ function versionInRange(version, range, options = {}) {
|
|||||||
: trimmedRange.replace(/,\s*/g, ' ');
|
: trimmedRange.replace(/,\s*/g, ' ');
|
||||||
// Validate version and range explicitly to enforce fail-closed semantics
|
// Validate version and range explicitly to enforce fail-closed semantics
|
||||||
// semver.satisfies() typically returns false for invalid inputs without throwing
|
// semver.satisfies() typically returns false for invalid inputs without throwing
|
||||||
const validVersion = semver.valid(trimmedVersion);
|
let validVersion = semver.valid(trimmedVersion);
|
||||||
const validRange = semver.validRange(semverRange);
|
const validRange = semver.validRange(semverRange);
|
||||||
|
// For fail-open mode (patch selection), try coercing invalid versions
|
||||||
|
// to handle common real-world formats like "8.0", date-based versions, etc.
|
||||||
|
if (!validVersion && !failClosed) {
|
||||||
|
const coerced = semver.coerce(trimmedVersion);
|
||||||
|
if (coerced) {
|
||||||
|
validVersion = coerced.version;
|
||||||
|
core.debug(`Coerced version "${trimmedVersion}" to "${validVersion}" for range matching`);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!validVersion || !validRange) {
|
if (!validVersion || !validRange) {
|
||||||
if (failClosed) {
|
if (failClosed) {
|
||||||
const issues = [];
|
const issues = [];
|
||||||
@@ -1803,6 +1813,38 @@ function countScorecardWarnings(scorecard, config) {
|
|||||||
: 0);
|
: 0);
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Execute promises with a concurrency limit to avoid overwhelming APIs.
|
||||||
|
* @param tasks - Array of functions that return promises
|
||||||
|
* @param limit - Maximum number of concurrent promises
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
const wrappedPromise = (() => __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const result = yield task();
|
||||||
|
results[index] = result;
|
||||||
|
}))();
|
||||||
|
executing.add(wrappedPromise);
|
||||||
|
// When promise completes, remove it from the executing set
|
||||||
|
wrappedPromise.finally(() => {
|
||||||
|
executing.delete(wrappedPromise);
|
||||||
|
});
|
||||||
|
// Wait if we've hit the concurrency limit
|
||||||
|
if (executing.size >= limit) {
|
||||||
|
yield Promise.race(executing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Wait for all remaining promises
|
||||||
|
yield Promise.all(executing);
|
||||||
|
return results;
|
||||||
|
});
|
||||||
|
}
|
||||||
function addChangeVulnerabilitiesToSummary(vulnerableChanges, severity) {
|
function addChangeVulnerabilitiesToSummary(vulnerableChanges, severity) {
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
if (vulnerableChanges.length === 0) {
|
if (vulnerableChanges.length === 0) {
|
||||||
@@ -1816,11 +1858,12 @@ function addChangeVulnerabilitiesToSummary(vulnerableChanges, severity) {
|
|||||||
advisorySet.add(vuln.advisory_ghsa_id);
|
advisorySet.add(vuln.advisory_ghsa_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Query GitHub API for patch info in parallel
|
// Query GitHub API for patch info with concurrency limiting
|
||||||
// Store all vulnerability entries (may be multiple per package with different ranges)
|
// Store all vulnerability entries (may be multiple per package with different ranges)
|
||||||
const patchInfo = {};
|
const patchInfo = {};
|
||||||
const apiClient = (0, utils_1.octokitClient)();
|
const apiClient = (0, utils_1.octokitClient)();
|
||||||
yield Promise.all(Array.from(advisorySet).map((advId) => __awaiter(this, void 0, void 0, function* () {
|
// Create tasks for promise pool
|
||||||
|
const tasks = Array.from(advisorySet).map(advId => () => __awaiter(this, void 0, void 0, function* () {
|
||||||
try {
|
try {
|
||||||
core.debug(`Fetching advisory data for ${advId}`);
|
core.debug(`Fetching advisory data for ${advId}`);
|
||||||
const apiResult = yield apiClient.request('GET /advisories/{ghsa_id}', {
|
const apiResult = yield apiClient.request('GET /advisories/{ghsa_id}', {
|
||||||
@@ -1855,7 +1898,9 @@ function addChangeVulnerabilitiesToSummary(vulnerableChanges, severity) {
|
|||||||
core.debug(`API call failed for ${advId}: ${errorMessage}`);
|
core.debug(`API call failed for ${advId}: ${errorMessage}`);
|
||||||
patchInfo[advId] = [];
|
patchInfo[advId] = [];
|
||||||
}
|
}
|
||||||
})));
|
}));
|
||||||
|
// Execute API calls with concurrency limit
|
||||||
|
yield promisePool(tasks, API_CONCURRENCY_LIMIT);
|
||||||
core.summary.addHeading('Vulnerabilities', 2);
|
core.summary.addHeading('Vulnerabilities', 2);
|
||||||
for (const manifest of manifests) {
|
for (const manifest of manifests) {
|
||||||
// Create fresh rows array for each manifest to avoid accumulation
|
// Create fresh rows array for each manifest to avoid accumulation
|
||||||
@@ -1898,7 +1943,11 @@ function addChangeVulnerabilitiesToSummary(vulnerableChanges, severity) {
|
|||||||
core.debug(`Found patch version ${patchVer} for ${change.name}@${change.version}`);
|
core.debug(`Found patch version ${patchVer} for ${change.name}@${change.version}`);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
core.debug(`No matching patch found for ${change.name}@${change.version}. Available entries: ${JSON.stringify(advisoryEntries)}`);
|
const maxLoggedEntries = 5;
|
||||||
|
const entriesPreview = advisoryEntries
|
||||||
|
.slice(0, maxLoggedEntries)
|
||||||
|
.map(entry => `${entry.eco}:${entry.pkg} ${entry.range} -> ${entry.patch}`);
|
||||||
|
core.debug(`No matching patch found for ${change.name}@${change.version}. Available entries (showing up to ${Math.min(advisoryEntries.length, maxLoggedEntries)} of ${advisoryEntries.length}): ${entriesPreview.join('; ')}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
+69
-9
@@ -17,6 +17,7 @@ const icons = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const MAX_SCANNED_FILES_BYTES = 1048576
|
const MAX_SCANNED_FILES_BYTES = 1048576
|
||||||
|
const API_CONCURRENCY_LIMIT = 10 // Limit concurrent API requests to avoid rate limiting
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper to check if a version falls within a vulnerable range.
|
* Helper to check if a version falls within a vulnerable range.
|
||||||
@@ -71,9 +72,21 @@ function versionInRange(
|
|||||||
|
|
||||||
// Validate version and range explicitly to enforce fail-closed semantics
|
// Validate version and range explicitly to enforce fail-closed semantics
|
||||||
// semver.satisfies() typically returns false for invalid inputs without throwing
|
// semver.satisfies() typically returns false for invalid inputs without throwing
|
||||||
const validVersion = semver.valid(trimmedVersion)
|
let validVersion = semver.valid(trimmedVersion)
|
||||||
const validRange = semver.validRange(semverRange)
|
const validRange = semver.validRange(semverRange)
|
||||||
|
|
||||||
|
// For fail-open mode (patch selection), try coercing invalid versions
|
||||||
|
// to handle common real-world formats like "8.0", date-based versions, etc.
|
||||||
|
if (!validVersion && !failClosed) {
|
||||||
|
const coerced = semver.coerce(trimmedVersion)
|
||||||
|
if (coerced) {
|
||||||
|
validVersion = coerced.version
|
||||||
|
core.debug(
|
||||||
|
`Coerced version "${trimmedVersion}" to "${validVersion}" for range matching`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!validVersion || !validRange) {
|
if (!validVersion || !validRange) {
|
||||||
if (failClosed) {
|
if (failClosed) {
|
||||||
const issues: string[] = []
|
const issues: string[] = []
|
||||||
@@ -228,6 +241,46 @@ function countScorecardWarnings(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute promises with a concurrency limit to avoid overwhelming APIs.
|
||||||
|
* @param tasks - Array of functions that return promises
|
||||||
|
* @param limit - Maximum number of concurrent promises
|
||||||
|
*/
|
||||||
|
async function promisePool<T>(
|
||||||
|
tasks: (() => Promise<T>)[],
|
||||||
|
limit: number
|
||||||
|
): Promise<T[]> {
|
||||||
|
const results: T[] = []
|
||||||
|
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
|
||||||
|
const wrappedPromise = (async () => {
|
||||||
|
const result = await task()
|
||||||
|
results[index] = result
|
||||||
|
})()
|
||||||
|
|
||||||
|
executing.add(wrappedPromise)
|
||||||
|
|
||||||
|
// When promise completes, remove it from the executing set
|
||||||
|
wrappedPromise.finally(() => {
|
||||||
|
executing.delete(wrappedPromise)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Wait if we've hit the concurrency limit
|
||||||
|
if (executing.size >= limit) {
|
||||||
|
await Promise.race(executing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for all remaining promises
|
||||||
|
await Promise.all(executing)
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
export async function addChangeVulnerabilitiesToSummary(
|
export async function addChangeVulnerabilitiesToSummary(
|
||||||
vulnerableChanges: Changes,
|
vulnerableChanges: Changes,
|
||||||
severity: string
|
severity: string
|
||||||
@@ -246,7 +299,7 @@ export async function addChangeVulnerabilitiesToSummary(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query GitHub API for patch info in parallel
|
// Query GitHub API for patch info with concurrency limiting
|
||||||
// Store all vulnerability entries (may be multiple per package with different ranges)
|
// Store all vulnerability entries (may be multiple per package with different ranges)
|
||||||
const patchInfo: Record<
|
const patchInfo: Record<
|
||||||
string,
|
string,
|
||||||
@@ -254,8 +307,8 @@ export async function addChangeVulnerabilitiesToSummary(
|
|||||||
> = {}
|
> = {}
|
||||||
const apiClient = octokitClient()
|
const apiClient = octokitClient()
|
||||||
|
|
||||||
await Promise.all(
|
// Create tasks for promise pool
|
||||||
Array.from(advisorySet).map(async advId => {
|
const tasks = Array.from(advisorySet).map(advId => async () => {
|
||||||
try {
|
try {
|
||||||
core.debug(`Fetching advisory data for ${advId}`)
|
core.debug(`Fetching advisory data for ${advId}`)
|
||||||
const apiResult = await apiClient.request('GET /advisories/{ghsa_id}', {
|
const apiResult = await apiClient.request('GET /advisories/{ghsa_id}', {
|
||||||
@@ -264,9 +317,7 @@ export async function addChangeVulnerabilitiesToSummary(
|
|||||||
|
|
||||||
patchInfo[advId] = []
|
patchInfo[advId] = []
|
||||||
const vulnList = apiResult.data.vulnerabilities || []
|
const vulnList = apiResult.data.vulnerabilities || []
|
||||||
core.debug(
|
core.debug(`Found ${vulnList.length} vulnerability entries for ${advId}`)
|
||||||
`Found ${vulnList.length} vulnerability entries for ${advId}`
|
|
||||||
)
|
|
||||||
|
|
||||||
for (const v of vulnList) {
|
for (const v of vulnList) {
|
||||||
if (v.package && v.package.ecosystem) {
|
if (v.package && v.package.ecosystem) {
|
||||||
@@ -297,7 +348,9 @@ export async function addChangeVulnerabilitiesToSummary(
|
|||||||
patchInfo[advId] = []
|
patchInfo[advId] = []
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
|
||||||
|
// Execute API calls with concurrency limit
|
||||||
|
await promisePool(tasks, API_CONCURRENCY_LIMIT)
|
||||||
|
|
||||||
core.summary.addHeading('Vulnerabilities', 2)
|
core.summary.addHeading('Vulnerabilities', 2)
|
||||||
|
|
||||||
@@ -359,8 +412,15 @@ export async function addChangeVulnerabilitiesToSummary(
|
|||||||
`Found patch version ${patchVer} for ${change.name}@${change.version}`
|
`Found patch version ${patchVer} for ${change.name}@${change.version}`
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
const maxLoggedEntries = 5
|
||||||
|
const entriesPreview = advisoryEntries
|
||||||
|
.slice(0, maxLoggedEntries)
|
||||||
|
.map(
|
||||||
|
entry =>
|
||||||
|
`${entry.eco}:${entry.pkg} ${entry.range} -> ${entry.patch}`
|
||||||
|
)
|
||||||
core.debug(
|
core.debug(
|
||||||
`No matching patch found for ${change.name}@${change.version}. Available entries: ${JSON.stringify(advisoryEntries)}`
|
`No matching patch found for ${change.name}@${change.version}. Available entries (showing up to ${Math.min(advisoryEntries.length, maxLoggedEntries)} of ${advisoryEntries.length}): ${entriesPreview.join('; ')}`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user