diff --git a/scripts/fetch-data.ts b/scripts/fetch-data.ts index 4c06fb0..40c1fd1 100644 --- a/scripts/fetch-data.ts +++ b/scripts/fetch-data.ts @@ -558,35 +558,51 @@ function replacePrivateImage(markdown: string, html: string): string { } async function extractModulePropsFromZip(downloadUrl: string): Promise> { - try { - // Extract module.prop content from zip URL (internal network, stable) - const { stdout: modulePropContent } = await execAsync(`runzip -p "${downloadUrl}" module.prop`, { - encoding: 'utf8', - maxBuffer: 64 * 1024 // 64KB buffer - }); + const maxRetries = 3; + let lastError: any; - // Parse module.prop content - const props: Record = {}; - if (!modulePropContent) return props; + for (let attempt = 1; attempt <= maxRetries; attempt++) { + try { + // Extract module.prop content from zip URL (internal network, stable) + const { stdout: modulePropContent } = await execAsync(`runzip -p "${downloadUrl}" module.prop`, { + encoding: 'utf8', + maxBuffer: 64 * 1024 // 64KB buffer + }); + + // Check if content is empty or only whitespace - treat as failure and retry + if (!modulePropContent || !modulePropContent.trim()) { + throw new Error(`Empty content returned from runzip for URL: ${downloadUrl}`); + } - const lines = modulePropContent.split('\n'); - for (const line of lines) { - const trimmed = line.trim(); - if (!trimmed || trimmed.startsWith('#')) continue; + // Parse module.prop content + const props: Record = {}; + const lines = modulePropContent.split('\n'); + for (const line of lines) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith('#')) continue; + + const eqIndex = trimmed.indexOf('='); + if (eqIndex > 0) { + const key = trimmed.substring(0, eqIndex).trim(); + const value = trimmed.substring(eqIndex + 1).trim(); + props[key] = value; + } + } - const eqIndex = trimmed.indexOf('='); - if (eqIndex > 0) { - const key = trimmed.substring(0, eqIndex).trim(); - const value = trimmed.substring(eqIndex + 1).trim(); - props[key] = value; + return props; + } catch (err: any) { + lastError = err; + if (attempt < maxRetries) { + console.warn(`Failed to extract props from ${downloadUrl} (attempt ${attempt}/${maxRetries}): ${err.message}, retrying...`); + // Add exponential backoff delay (1s, 2s) before retrying to handle transient issues + await new Promise(resolve => setTimeout(resolve, 1000 * attempt)); + } else { + console.error(`Failed to extract props from ${downloadUrl} after ${maxRetries} attempts: ${err.message}`); } } - - return props; - } catch (err: any) { - console.error(`Failed to extract props from ${downloadUrl}: ${err.message}`); - return {}; } + + return {}; } const RESERVED_NAMES = ['.github', 'submission', 'developers', 'modules', 'org.kernelsu.example', "module_release"];