@@ -55685,6 +55685,8 @@ function findRhelRelease(semanticVersionSpec, architecture, manifest, osVersion)
5568555685 }
5568655686 return undefined;
5568755687}
55688+ const MANIFEST_FETCH_MAX_ATTEMPTS = 3;
55689+ const MANIFEST_FETCH_RETRY_BASE_DELAY_MS = 1000;
5568855690async function findReleaseFromManifest(semanticVersionSpec, architecture, manifest) {
5568955691 if (!manifest) {
5569055692 manifest = await getManifest();
@@ -55712,26 +55714,72 @@ function isIToolRelease(obj) {
5571255714 typeof file.arch === 'string' &&
5571355715 typeof file.download_url === 'string'));
5571455716}
55717+ // Rejects empty or truncated manifest responses.
55718+ function isValidManifest(manifest) {
55719+ return (Array.isArray(manifest) &&
55720+ manifest.length > 0 &&
55721+ manifest.every(isIToolRelease));
55722+ }
55723+ function sleep(ms) {
55724+ return new Promise(resolve => setTimeout(resolve, ms));
55725+ }
55726+ // HTTP 403/429 from http-client (`statusCode`) or tool-cache (`httpStatusCode`).
55727+ function isRateLimitError(err) {
55728+ const e = err;
55729+ const status = e?.httpStatusCode ?? e?.statusCode;
55730+ return status === 403 || status === 429;
55731+ }
55732+ // Fetches and validates a manifest, retrying transient failures with backoff.
55733+ async function fetchValidManifest(source, fetcher) {
55734+ let lastError;
55735+ let attempts = 0;
55736+ for (let attempt = 1; attempt <= MANIFEST_FETCH_MAX_ATTEMPTS; attempt++) {
55737+ attempts = attempt;
55738+ try {
55739+ const manifest = await fetcher();
55740+ if (isValidManifest(manifest)) {
55741+ return manifest;
55742+ }
55743+ throw new Error(`The manifest fetched from ${source} is empty, truncated, or does not contain any valid tool release entries.`);
55744+ }
55745+ catch (err) {
55746+ lastError = err instanceof Error ? err : new Error(String(err));
55747+ core.debug(`Attempt ${attempt}/${MANIFEST_FETCH_MAX_ATTEMPTS} to fetch the manifest from ${source} failed: ${lastError.message}`);
55748+ // Rate limits won't clear within the backoff window; fall back instead.
55749+ if (isRateLimitError(err)) {
55750+ core.debug(`${source} is rate-limited; skipping retries for this source.`);
55751+ break;
55752+ }
55753+ if (attempt < MANIFEST_FETCH_MAX_ATTEMPTS) {
55754+ const delay = MANIFEST_FETCH_RETRY_BASE_DELAY_MS * 2 ** (attempt - 1);
55755+ core.debug(`Retrying in ${delay}ms...`);
55756+ await sleep(delay);
55757+ }
55758+ }
55759+ }
55760+ throw new Error(`Failed to fetch a valid manifest from ${source} after ${attempts} attempt(s): ${lastError?.message}`);
55761+ }
5571555762async function getManifest() {
5571655763 try {
55717- const repoManifest = await getManifestFromRepo();
55718- if (Array.isArray(repoManifest) &&
55719- repoManifest.length &&
55720- repoManifest.every(isIToolRelease)) {
55721- return repoManifest;
55722- }
55723- throw new Error('The repository manifest is invalid or does not include any valid tool release (IToolRelease) entries.');
55764+ return await fetchValidManifest('the GitHub API', getManifestFromRepo);
5572455765 }
5572555766 catch (err) {
5572655767 core.debug('Fetching the manifest via the API failed.');
5572755768 if (err instanceof Error) {
5572855769 core.debug(err.message);
5572955770 }
5573055771 else {
55731- core.error ('An unexpected error occurred while fetching the manifest.');
55772+ core.debug ('An unexpected error occurred while fetching the manifest.');
5573255773 }
5573355774 }
55734- return await getManifestFromURL();
55775+ try {
55776+ return await fetchValidManifest('the raw URL', getManifestFromURL);
55777+ }
55778+ catch (err) {
55779+ const message = err instanceof Error ? err.message : String(err);
55780+ // Fail loudly so the action doesn't exit 0 without installing Python.
55781+ throw new Error(`Failed to fetch the Python versions manifest. The response was empty, truncated, or invalid, and all retries were exhausted. ${message}`);
55782+ }
5573555783}
5573655784function getManifestFromRepo() {
5573755785 core.debug(`Getting manifest from ${MANIFEST_REPO_OWNER}/${MANIFEST_REPO_NAME}@${MANIFEST_REPO_BRANCH}`);
0 commit comments