|
| 1 | +const { chromium } = require('playwright'); |
| 2 | +const fs = require('fs'); |
| 3 | +const path = require('path'); |
| 4 | + |
| 5 | +async function generateOGImages() { |
| 6 | + console.log('🎨 Starting OG image generation...'); |
| 7 | + |
| 8 | + let browser; |
| 9 | + try { |
| 10 | + browser = await chromium.launch({ |
| 11 | + headless: true, |
| 12 | + args: [ |
| 13 | + '--no-sandbox', |
| 14 | + '--disable-setuid-sandbox', |
| 15 | + '--disable-web-security', |
| 16 | + '--disable-features=VizDisplayCompositor', |
| 17 | + '--disable-background-timer-throttling', |
| 18 | + '--disable-backgrounding-occluded-windows', |
| 19 | + '--disable-renderer-backgrounding' |
| 20 | + ] |
| 21 | + }); |
| 22 | + } catch (error) { |
| 23 | + console.error('❌ Failed to launch browser:', error.message); |
| 24 | + console.log('💡 Please run "npx playwright install chromium" to install the browser'); |
| 25 | + return; |
| 26 | + } |
| 27 | + |
| 28 | + try { |
| 29 | + const distPath = path.join(__dirname, '..', 'dist'); |
| 30 | + const ogApiPath = path.join(distPath, 'api', 'og'); |
| 31 | + |
| 32 | + if (!fs.existsSync(ogApiPath)) { |
| 33 | + console.log('❌ No OG API directory found in dist. Make sure to run astro build first.'); |
| 34 | + return; |
| 35 | + } |
| 36 | + |
| 37 | + const htmlFiles = findHtmlFiles(ogApiPath); |
| 38 | + console.log(`📁 Found ${htmlFiles.length} OG image files to convert`); |
| 39 | + |
| 40 | + // Process in parallel with limited concurrency for better performance |
| 41 | + const BATCH_SIZE = 5; |
| 42 | + let converted = 0; |
| 43 | + let failed = 0; |
| 44 | + |
| 45 | + for (let i = 0; i < htmlFiles.length; i += BATCH_SIZE) { |
| 46 | + const batch = htmlFiles.slice(i, i + BATCH_SIZE); |
| 47 | + const results = await Promise.allSettled( |
| 48 | + batch.map(htmlFile => processFile(browser, htmlFile, distPath)) |
| 49 | + ); |
| 50 | + |
| 51 | + results.forEach((result, index) => { |
| 52 | + if (result.status === 'fulfilled') { |
| 53 | + converted++; |
| 54 | + } else { |
| 55 | + console.error(`❌ Failed to convert ${path.relative(distPath, batch[index])}: ${result.reason}`); |
| 56 | + failed++; |
| 57 | + } |
| 58 | + }); |
| 59 | + |
| 60 | + console.log(`✅ Processed batch ${Math.floor(i/BATCH_SIZE) + 1}/${Math.ceil(htmlFiles.length/BATCH_SIZE)}`); |
| 61 | + } |
| 62 | + |
| 63 | + console.log(`✅ Successfully converted ${converted} images to PNG format`); |
| 64 | + if (failed > 0) { |
| 65 | + console.log(`⚠️ Failed to convert ${failed} images`); |
| 66 | + } |
| 67 | + |
| 68 | + } catch (error) { |
| 69 | + console.error('❌ Error during OG image generation:', error); |
| 70 | + } finally { |
| 71 | + await browser.close(); |
| 72 | + } |
| 73 | +} |
| 74 | + |
| 75 | +async function processFile(browser, htmlFile, distPath) { |
| 76 | + const page = await browser.newPage(); |
| 77 | + |
| 78 | + try { |
| 79 | + await page.setViewportSize({ width: 1200, height: 630 }); |
| 80 | + |
| 81 | + // Read and optimize HTML content |
| 82 | + let htmlContent = fs.readFileSync(htmlFile, 'utf8'); |
| 83 | + htmlContent = htmlContent.replace( |
| 84 | + /@import url\('https:\/\/fonts\.googleapis\.com\/[^']+'\);/g, |
| 85 | + '' |
| 86 | + ); |
| 87 | + htmlContent = htmlContent.replace( |
| 88 | + /font-family: 'Inter'[^;]+;/g, |
| 89 | + "font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;" |
| 90 | + ); |
| 91 | + |
| 92 | + await page.setContent(htmlContent, { |
| 93 | + waitUntil: 'domcontentloaded', |
| 94 | + timeout: 5000 |
| 95 | + }); |
| 96 | + |
| 97 | + // Minimal wait for rendering |
| 98 | + await page.waitForTimeout(250); |
| 99 | + |
| 100 | + const screenshot = await page.screenshot({ |
| 101 | + type: 'png', |
| 102 | + fullPage: false, |
| 103 | + clip: { x: 0, y: 0, width: 1200, height: 630 } |
| 104 | + }); |
| 105 | + |
| 106 | + fs.writeFileSync(htmlFile, screenshot); |
| 107 | + console.log(`🖼️ Converted: ${path.relative(distPath, htmlFile)}`); |
| 108 | + |
| 109 | + } finally { |
| 110 | + await page.close(); |
| 111 | + } |
| 112 | +} |
| 113 | + |
| 114 | +function findHtmlFiles(dir) { |
| 115 | + const files = []; |
| 116 | + |
| 117 | + function scan(currentDir) { |
| 118 | + const items = fs.readdirSync(currentDir); |
| 119 | + |
| 120 | + for (const item of items) { |
| 121 | + const fullPath = path.join(currentDir, item); |
| 122 | + const stat = fs.statSync(fullPath); |
| 123 | + |
| 124 | + if (stat.isDirectory()) { |
| 125 | + scan(fullPath); |
| 126 | + } else if (item.endsWith('.png')) { |
| 127 | + try { |
| 128 | + const content = fs.readFileSync(fullPath, 'utf8'); |
| 129 | + if (content.trim().startsWith('<!DOCTYPE html>')) { |
| 130 | + files.push(fullPath); |
| 131 | + } |
| 132 | + } catch (error) { |
| 133 | + // Skip files that can't be read as text |
| 134 | + } |
| 135 | + } |
| 136 | + } |
| 137 | + } |
| 138 | + |
| 139 | + scan(dir); |
| 140 | + return files; |
| 141 | +} |
| 142 | + |
| 143 | +generateOGImages().catch(console.error); |
0 commit comments