Skip to content

Commit 4f7c79f

Browse files
committed
refactor: enhance release asset validation
1 parent 53c83b9 commit 4f7c79f

1 file changed

Lines changed: 79 additions & 48 deletions

File tree

lib/extension.js

Lines changed: 79 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -318,34 +318,73 @@ class CppReferenceExtension {
318318
const isDevelopOnly = this.config.version === 'develop'
319319

320320
// Sort the releases by the date of their latest asset
321-
releasesInfo.sort((a, b) => {
322-
// check if at least one asset exists
323-
if (!a.assets) {
324-
return 1
325-
}
326-
if (!b.assets) {
327-
return -1
321+
const releasesArray = Array.isArray(releasesInfo) ? releasesInfo : []
322+
if (!Array.isArray(releasesInfo)) {
323+
this.logger.warn('GitHub releases response is not an array. Skipping malformed response.')
324+
return
325+
}
326+
327+
const sanitizedReleases = []
328+
releasesArray.forEach((release, releaseIndex) => {
329+
if (!release || typeof release !== 'object') {
330+
this.logger.debug(`Skipping release at index ${releaseIndex} because it is not an object.`)
331+
return
328332
}
329-
// check if at least one asset has an updated_at field
330-
if (!a.assets.some(asset => asset.updated_at)) {
331-
return 1
333+
334+
const releaseTag = typeof release.tag_name === 'string' && release.tag_name.trim()
335+
? release.tag_name.trim()
336+
: null
337+
if (!releaseTag) {
338+
this.logger.debug(`Skipping release at index ${releaseIndex} because tag_name is missing or invalid.`)
339+
return
332340
}
333-
if (!b.assets.some(asset => asset.updated_at)) {
334-
return -1
341+
342+
const rawAssets = Array.isArray(release.assets) ? release.assets : []
343+
if (!Array.isArray(release.assets)) {
344+
this.logger.debug(`Skipping release ${releaseTag} because assets is not an array.`)
345+
return
335346
}
336347

337-
// Find the latest asset in each release
338-
const latestAssetA = a.assets.reduce((latest, asset) => {
339-
return new Date(asset.updated_at) > new Date(latest.updated_at) ? asset : latest;
340-
}, a.assets[0]);
348+
const validAssets = []
349+
rawAssets.forEach((asset, assetIndex) => {
350+
if (!asset || typeof asset !== 'object') {
351+
this.logger.debug(`Skipping asset ${releaseTag}#${assetIndex} because it is not an object.`)
352+
return
353+
}
354+
355+
const downloadUrl = typeof asset.browser_download_url === 'string' && asset.browser_download_url.trim()
356+
? asset.browser_download_url.trim()
357+
: null
358+
if (!downloadUrl) {
359+
this.logger.debug(`Skipping asset ${releaseTag}#${assetIndex} because browser_download_url is missing or invalid.`)
360+
return
361+
}
362+
363+
const updatedAtRaw = asset.updated_at
364+
if (!updatedAtRaw) {
365+
this.logger.debug(`Skipping asset ${downloadUrl} because updated_at is missing.`)
366+
return
367+
}
368+
369+
const updatedAt = new Date(updatedAtRaw)
370+
if (Number.isNaN(updatedAt.getTime())) {
371+
this.logger.debug(`Skipping asset ${downloadUrl} because updated_at "${updatedAtRaw}" is not a valid date.`)
372+
return
373+
}
374+
375+
validAssets.push({asset, updatedAt, downloadUrl})
376+
})
341377

342-
const latestAssetB = b.assets.reduce((latest, asset) => {
343-
return new Date(asset.updated_at) > new Date(latest.updated_at) ? asset : latest;
344-
}, b.assets[0]);
378+
if (!validAssets.length) {
379+
this.logger.debug(`Skipping release ${releaseTag} because it has no valid assets.`)
380+
return
381+
}
345382

346-
// Sort the releases by the date of their latest asset
347-
return new Date(latestAssetB.updated_at) - new Date(latestAssetA.updated_at);
348-
});
383+
validAssets.sort((a, b) => b.updatedAt - a.updatedAt)
384+
sanitizedReleases.push({tag: releaseTag, assets: validAssets})
385+
})
386+
387+
sanitizedReleases.sort((a, b) => b.assets[0].updatedAt - a.assets[0].updatedAt)
349388

350389
// Determine the appropriate suffix for the current platform
351390
let platformSuffix;
@@ -363,7 +402,7 @@ class CppReferenceExtension {
363402
throw new Error(`Unsupported platform: ${process.platform}. Supported platforms are win32, linux, and darwin.`);
364403
}
365404

366-
function isReleaseMatch(releaseTag, logger) {
405+
const isReleaseMatch = (releaseTag, logger) => {
367406
if (isMatchAny) {
368407
logger.debug(`Matching any version. Found a release that satisfies the version requirement: ${releaseTag}`)
369408
return true;
@@ -405,34 +444,21 @@ class CppReferenceExtension {
405444
}
406445

407446
// Iterate over the releases
408-
for (const release of releasesInfo) {
409-
if (!isReleaseMatch(release.tag_name, this.logger)) {
447+
for (const release of sanitizedReleases) {
448+
if (!isReleaseMatch(release.tag, this.logger)) {
410449
continue
411450
}
412451

413-
if (!release.assets) {
414-
this.logger.debug(`Skipping release ${release.tag_name} as it has no assets`)
415-
continue
416-
}
417-
418-
if (!release.assets.every(asset => asset.updated_at)) {
419-
this.logger.debug(`Skipping release ${release.tag_name} as it has assets without an updated_at field`)
420-
continue
421-
}
422-
423-
// Sort release assets descending by updated_at
424-
release.assets.sort((a, b) => new Date(b.updated_at) - new Date(a.updated_at));
425-
426452
// Iterate over the assets of the release
427-
for (const asset of release.assets) {
453+
for (const {asset, downloadUrl} of release.assets) {
428454
// Check if the asset URL ends with the appropriate suffix for the platform
429-
if (asset.browser_download_url.endsWith(platformSuffix)) {
455+
if (downloadUrl.endsWith(platformSuffix)) {
430456
// Return the URL of the asset and the tag name of the release
431-
this.MrDocsVersion = release.tag_name
432-
this.logger.debug(`Found asset ${asset.browser_download_url} for platform ${process.platform}`)
433-
return {downloadUrl: asset.browser_download_url, downloadTag: release.tag_name};
457+
this.MrDocsVersion = release.tag
458+
this.logger.debug(`Found asset ${downloadUrl} for platform ${process.platform}`)
459+
return {downloadUrl, downloadTag: release.tag};
434460
}
435-
this.logger.debug(`Skipping asset ${asset.browser_download_url} as it doesn't match the platform suffix ${platformSuffix}`)
461+
this.logger.debug(`Skipping asset ${downloadUrl} as it doesn't match the platform suffix ${platformSuffix}`)
436462
}
437463
}
438464

@@ -630,11 +656,16 @@ class CppReferenceExtension {
630656
'User-Agent': 'request'
631657
}
632658
const releasesResponse = await axios.get('https://api.github.com/repos/cppalliance/mrdocs/releases', {headers: apiHeaders})
633-
const releasesInfo = releasesResponse.data
659+
const releasesInfo = Array.isArray(releasesResponse && releasesResponse.data)
660+
? releasesResponse.data
661+
: []
662+
if (!Array.isArray(releasesResponse && releasesResponse.data)) {
663+
this.logger.warn('GitHub releases API did not return an array. Continuing with an empty list.')
664+
}
634665
this.logger.debug(`Found ${releasesInfo.length} MrDocs releases`)
635666
const { downloadUrl, downloadTag } = this.findDownloadUrl(releasesInfo)
636-
if (!downloadUrl) {
637-
this.logger.error(`Could not find MrDocs binaries for ${process.platform}`)
667+
if (!downloadUrl || !downloadTag) {
668+
this.logger.error(`Could not find MrDocs binaries for ${process.platform}.`)
638669
process.exit(1)
639670
}
640671
const mrdocsDownloadDir = path.join(mrDocsTreeDir, process.platform)
@@ -1861,4 +1892,4 @@ class CppReferenceExtension {
18611892
}
18621893
}
18631894

1864-
module.exports = CppReferenceExtension
1895+
module.exports = CppReferenceExtension

0 commit comments

Comments
 (0)