Skip to content

Commit 0265453

Browse files
fix: improve error handling in get-modules script
Enhances robustness of module cloning to prevent pipeline failures when individual repositories cannot be cloned. **Key changes:** - Add try-finally block to ensure output files are always written - Implement consecutive error detection (warns after 10 failures) - Enhance error logging with module position, stack traces, and context - Make stage 3 validation non-blocking - Store detailed error info in skipped_modules.json **Impact:** Prevents "ENOENT: no such file or directory" errors causing workflow failures. Pipeline continues even when some modules fail to clone. Fixes frequent container-build workflow failures.
1 parent 9ccdb55 commit 0265453

File tree

1 file changed

+132
-71
lines changed

1 file changed

+132
-71
lines changed

scripts/get-modules.ts

Lines changed: 132 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,7 @@ function createSkippedEntry(
403403
statusText?: string;
404404
responseSnippet?: string;
405405
initialStatusCode?: number | null;
406+
error?: string;
406407
} = {}
407408
) {
408409
const owner = extractOwnerFromUrl(module.url);
@@ -442,12 +443,24 @@ async function refreshRepository({
442443
? module.branch
443444
: undefined;
444445

445-
await ensureRepository({
446-
repositoryUrl: module.url,
447-
directoryPath: tempPath,
448-
branch,
449-
depth: 1
450-
});
446+
try {
447+
await ensureRepository({
448+
repositoryUrl: module.url,
449+
directoryPath: tempPath,
450+
branch,
451+
depth: 1
452+
});
453+
} catch (error) {
454+
// Add context to the error for better debugging
455+
const message = error instanceof Error ? error.message : String(error);
456+
const enhancedError = new Error(
457+
`Failed to clone repository ${module.url}${branch ? ` (branch: ${branch})` : ""}: ${message}`
458+
);
459+
if (error instanceof Error && error.stack) {
460+
enhancedError.stack = error.stack;
461+
}
462+
throw enhancedError;
463+
}
451464

452465
await ensureDirectory(path.dirname(finalPath));
453466
if (await fileExists(finalPath)) {
@@ -464,17 +477,30 @@ async function writeOutputs({
464477
validModules: ModuleEntry[];
465478
skippedModules: ReturnType<typeof createSkippedEntry>[];
466479
}) {
480+
// Always write the stage 3 file, even if no modules were successfully cloned
481+
// This allows the pipeline to continue even if this stage had issues
482+
logger.info(`Writing stage 3 output with ${validModules.length} valid modules`);
467483
await writeJson(
468484
MODULES_STAGE_3_PATH,
469485
{ modules: validModules },
470486
{ pretty: 2 }
471487
);
472488

473489
if (skippedModules.length > 0) {
490+
logger.info(`Writing ${skippedModules.length} skipped modules to ${SKIPPED_MODULES_PATH}`);
474491
await writeJson(SKIPPED_MODULES_PATH, skippedModules, { pretty: 2 });
475492
}
476493

477-
await validateStageFile("modules.stage.3", MODULES_STAGE_3_PATH);
494+
// Validate the output file - this will throw if the file is invalid
495+
// but at least the file exists now
496+
try {
497+
await validateStageFile("modules.stage.3", MODULES_STAGE_3_PATH);
498+
logger.info("Stage 3 output file validated successfully");
499+
} catch (validationError) {
500+
logger.warn("Stage 3 output file validation failed, but file was written");
501+
logErrorDetails(validationError, { scope: "Stage 3 validation" });
502+
// Don't throw here - we want the file to exist even if validation fails
503+
}
478504
}
479505

480506
async function processModules() {
@@ -508,83 +534,118 @@ async function processModules() {
508534
const validModules: ModuleEntry[] = [];
509535
const skippedModules: ReturnType<typeof createSkippedEntry>[] = [];
510536
let moduleCounter = 0;
537+
let consecutiveErrors = 0;
538+
const MAX_CONSECUTIVE_ERRORS = 10;
511539

512540
await ensureDirectory(MODULES_DIR);
513541

514-
for (const {
515-
module,
516-
ok,
517-
statusCode,
518-
statusText,
519-
responseSnippet,
520-
usedFallback,
521-
initialStatusCode
522-
} of validated) {
523-
if (!ok) {
524-
skippedModules.push(
525-
createSkippedEntry(module, "Invalid repository URL", "invalid_url", {
526-
statusCode,
527-
statusText,
528-
responseSnippet,
529-
initialStatusCode
530-
})
531-
);
532-
continue;
533-
}
542+
// Use try-finally to ensure output is written even if errors occur
543+
try {
544+
for (const {
545+
module,
546+
ok,
547+
statusCode,
548+
statusText,
549+
responseSnippet,
550+
usedFallback,
551+
initialStatusCode
552+
} of validated) {
553+
if (!ok) {
554+
skippedModules.push(
555+
createSkippedEntry(module, "Invalid repository URL", "invalid_url", {
556+
statusCode,
557+
statusText,
558+
responseSnippet,
559+
initialStatusCode
560+
})
561+
);
562+
continue;
563+
}
534564

535-
const owner = extractOwnerFromUrl(module.url);
536-
const identifier = `${module.name}-----${owner}`;
537-
const tempPath = path.join(MODULES_TEMP_DIR, identifier);
538-
const finalPath = path.join(MODULES_DIR, identifier);
565+
const owner = extractOwnerFromUrl(module.url);
566+
const identifier = `${module.name}-----${owner}`;
567+
const tempPath = path.join(MODULES_TEMP_DIR, identifier);
568+
const finalPath = path.join(MODULES_DIR, identifier);
539569

540-
const moduleCopy: ModuleEntry = { ...module };
570+
const moduleCopy: ModuleEntry = { ...module };
541571

542-
if (statusCode && REDIRECT_STATUS_CODES.has(statusCode)) {
543-
ensureIssueArray(moduleCopy);
544-
moduleCopy.issues?.push(
545-
statusCode === 301
546-
? "The repository URL returns a 301 status code, indicating it has been moved. Please verify the new location and update the module list if necessary."
547-
: `The repository URL returned a ${statusCode} redirect during validation. Please confirm the final destination and update the module list if necessary.`
548-
);
549-
}
572+
if (statusCode && REDIRECT_STATUS_CODES.has(statusCode)) {
573+
ensureIssueArray(moduleCopy);
574+
moduleCopy.issues?.push(
575+
statusCode === 301
576+
? "The repository URL returns a 301 status code, indicating it has been moved. Please verify the new location and update the module list if necessary."
577+
: `The repository URL returned a ${statusCode} redirect during validation. Please confirm the final destination and update the module list if necessary.`
578+
);
579+
}
550580

551-
if (usedFallback) {
552-
ensureIssueArray(moduleCopy);
553-
moduleCopy.issues?.push(
554-
"HEAD requests to this repository failed but a subsequent GET request succeeded. Please verify the repository URL and server configuration."
581+
if (usedFallback) {
582+
ensureIssueArray(moduleCopy);
583+
moduleCopy.issues?.push(
584+
"HEAD requests to this repository failed but a subsequent GET request succeeded. Please verify the repository URL and server configuration."
585+
);
586+
}
587+
588+
moduleCounter += 1;
589+
logger.info(
590+
`+++ ${moduleCounter.toString().padStart(4, " ")}: ${module.name} by ${owner} - ${module.url}`
555591
);
556-
}
557592

558-
moduleCounter += 1;
559-
logger.info(
560-
`+++ ${moduleCounter.toString().padStart(4, " ")}: ${module.name} by ${owner} - ${module.url}`
561-
);
593+
try {
594+
await refreshRepository({ module: moduleCopy, tempPath, finalPath });
595+
validModules.push(moduleCopy);
596+
consecutiveErrors = 0; // Reset error counter on success
597+
} catch (error) {
598+
consecutiveErrors += 1;
599+
const message = error instanceof Error ? error.message : String(error);
600+
const errorStack = error instanceof Error ? error.stack : undefined;
601+
602+
logger.error(
603+
`Failed to clone/update module [${moduleCounter}/${validated.length}]: ${module.name} (${module.url})`
604+
);
605+
logger.error(`Error: ${message}`);
606+
607+
if (errorStack && errorStack !== message) {
608+
logger.debug(`Stack trace: ${errorStack}`);
609+
}
610+
611+
await rm(tempPath, { recursive: true, force: true }).catch(() => {});
612+
skippedModules.push(
613+
createSkippedEntry(
614+
module,
615+
"Repository clone failed - URL might be invalid or repository might be private/deleted",
616+
"clone_failure",
617+
{ error: message }
618+
)
619+
);
620+
621+
// If we have too many consecutive errors, something might be systematically wrong
622+
if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
623+
logger.warn(
624+
`Encountered ${MAX_CONSECUTIVE_ERRORS} consecutive errors. Continuing but this might indicate a systematic issue (e.g., network problems, rate limiting).`
625+
);
626+
// Reset counter to avoid spamming this warning
627+
consecutiveErrors = 0;
628+
}
629+
630+
continue;
631+
}
632+
}
562633

634+
logger.info(`Modules cloned: ${validModules.length}`);
635+
logger.info(`Modules skipped: ${skippedModules.length}`);
636+
logger.info(`Total modules processed: ${validModules.length + skippedModules.length}/${validated.length}`);
637+
} finally {
638+
// Always write output, even if errors occurred
639+
logger.info("Writing output files...");
563640
try {
564-
await refreshRepository({ module: moduleCopy, tempPath, finalPath });
565-
validModules.push(moduleCopy);
566-
} catch (error) {
567-
const message = error instanceof Error ? error.message : String(error);
568-
logger.error(
569-
`Failed to clone/update module: ${module.name} (${module.url})`
570-
);
571-
logger.error(message);
572-
await rm(tempPath, { recursive: true, force: true }).catch(() => {});
573-
skippedModules.push(
574-
createSkippedEntry(
575-
module,
576-
"Repository clone failed - URL might be invalid or repository might be private/deleted",
577-
"clone_failure"
578-
)
579-
);
580-
continue;
641+
await writeOutputs({ validModules, skippedModules });
642+
logger.info("Output files written successfully");
643+
} catch (writeError) {
644+
logger.error("Failed to write output files");
645+
logErrorDetails(writeError, { scope: "writeOutputs" });
646+
throw writeError;
581647
}
582648
}
583-
584-
logger.info(`Modules cloned: ${validModules.length}`);
585-
logger.info(`Modules skipped: ${skippedModules.length}`);
586-
587-
await writeOutputs({ validModules, skippedModules });
588649
}
589650

590651
async function main() {

0 commit comments

Comments
 (0)