From 049db7424f8170b00962615a643eca04362978a4 Mon Sep 17 00:00:00 2001 From: Colin Date: Sun, 30 Nov 2025 21:30:49 -0500 Subject: [PATCH] bump --- docker/generate-pdfs.js | 111 ++++++++++++++++++++++++++++++++++------ 1 file changed, 96 insertions(+), 15 deletions(-) diff --git a/docker/generate-pdfs.js b/docker/generate-pdfs.js index 788a429..0bf0bdd 100644 --- a/docker/generate-pdfs.js +++ b/docker/generate-pdfs.js @@ -122,27 +122,108 @@ async function generatePdf(browser, htmlFile) { timeout: 30000 }); - // Wait a bit for any JavaScript to finish + // Wait for includes.js to finish loading header and footer + // Check if header-include element exists and has content + try { + await page.waitForFunction(() => { + const headerInclude = document.getElementById('header-include'); + const footerInclude = document.getElementById('footer-include'); + + // If neither element exists, page doesn't use includes - that's fine + if (!headerInclude && !footerInclude) return true; + + // If header exists but is empty, wait + if (headerInclude && headerInclude.innerHTML.trim() === '') return false; + + // If footer exists but is empty, wait + if (footerInclude && footerInclude.innerHTML.trim() === '') return false; + + // If header exists, check if nav is loaded (indicates includes.js finished) + if (headerInclude) { + const nav = headerInclude.querySelector('nav'); + if (!nav) return false; + } + + return true; + }, { + timeout: 15000, + polling: 100 // Check every 100ms + }); + } catch (waitError) { + // If waiting for includes times out, continue anyway + // Some pages might not use includes or might load differently + console.warn(`Warning: Includes may not have fully loaded for ${htmlFile}, continuing anyway...`); + } + + // Additional wait for any remaining JavaScript to finish await page.waitForTimeout(1000); - // Generate PDF - await page.pdf({ - path: pdfPath, - format: 'A4', - printBackground: true, - margin: { - top: '20mm', - right: '15mm', - bottom: '20mm', - left: '15mm' - } - }); + // Generate PDF with retry logic for transient errors + let retries = 2; + let lastError = null; - console.log(`✓ Generated: ${pdfRelativePath}`); + while (retries > 0) { + try { + await page.pdf({ + path: pdfPath, + format: 'A4', + printBackground: true, + margin: { + top: '20mm', + right: '15mm', + bottom: '20mm', + left: '15mm' + } + }); + + console.log(`✓ Generated: ${pdfRelativePath}`); + return; // Success, exit retry loop + } catch (pdfError) { + lastError = pdfError; + // Check if it's a recoverable error + if (pdfError.message.includes('detached') || + pdfError.message.includes('closed') || + pdfError.message.includes('Target closed')) { + retries--; + if (retries > 0) { + console.warn(`Retrying PDF generation for ${htmlFile} (${retries} attempts remaining)...`); + // Wait a bit before retrying + await page.waitForTimeout(2000); + // Re-navigate to the page if it was closed + try { + await page.goto(url, { + waitUntil: 'networkidle0', + timeout: 30000 + }); + await page.waitForTimeout(1000); + } catch (navError) { + // If navigation fails, break out of retry loop + break; + } + } + } else { + // Non-recoverable error, don't retry + throw pdfError; + } + } + } + + // If we get here, all retries failed + throw lastError || new Error('PDF generation failed after retries'); } catch (error) { console.error(`✗ Failed: ${htmlFile} - ${error.message}`); } finally { - await page.close(); + try { + await page.close(); + } catch (closeError) { + // Ignore close errors - page may have been closed already + // This can happen if the page navigated or was closed during processing + // Common error: "Protocol error: Connection closed" or "Target closed" + if (!closeError.message.includes('closed') && !closeError.message.includes('Target closed')) { + // Only log if it's not a "page already closed" error + console.warn(`Warning closing page for ${htmlFile}: ${closeError.message}`); + } + } } }