| 1 | #!/usr/bin/env bun |
| 2 | |
| 3 | import { readdirSync, statSync } from "node:fs"; |
| 4 | import { join } from "node:path"; |
| 5 | |
| 6 | const distDir = "./docs/dist"; |
| 7 | const ogImageUrl = "https://sequoia.pub/og.png"; |
| 8 | |
| 9 | // Function to recursively find all HTML files |
| 10 | function findHtmlFiles(dir: string): string[] { |
| 11 | const files: string[] = []; |
| 12 | const entries = readdirSync(dir); |
| 13 | |
| 14 | for (const entry of entries) { |
| 15 | const fullPath = join(dir, entry); |
| 16 | const stat = statSync(fullPath); |
| 17 | |
| 18 | if (stat.isDirectory()) { |
| 19 | files.push(...findHtmlFiles(fullPath)); |
| 20 | } else if (entry.endsWith(".html")) { |
| 21 | files.push(fullPath); |
| 22 | } |
| 23 | } |
| 24 | |
| 25 | return files; |
| 26 | } |
| 27 | |
| 28 | // Function to inject OG image meta tags |
| 29 | async function injectOgImageTags(filePath: string) { |
| 30 | const file = Bun.file(filePath); |
| 31 | let content = await file.text(); |
| 32 | |
| 33 | // Check if og:image already exists |
| 34 | if (content.includes('property="og:image"')) { |
| 35 | console.log(`⏭️ Skipping ${filePath} - og:image already exists`); |
| 36 | return; |
| 37 | } |
| 38 | |
| 39 | // Find the position to inject the meta tag |
| 40 | // We'll insert it after og:description if it exists, or before twitter:card |
| 41 | const ogDescriptionMatch = content.match( |
| 42 | /<meta property="og:description"[^>]*>/, |
| 43 | ); |
| 44 | const twitterCardMatch = content.match(/<meta name="twitter:card"[^>]*>/); |
| 45 | |
| 46 | let insertPosition: number; |
| 47 | if (ogDescriptionMatch && ogDescriptionMatch.index !== undefined) { |
| 48 | insertPosition = ogDescriptionMatch.index + ogDescriptionMatch[0].length; |
| 49 | } else if (twitterCardMatch && twitterCardMatch.index !== undefined) { |
| 50 | insertPosition = twitterCardMatch.index; |
| 51 | } else { |
| 52 | // Fallback: insert before </head> |
| 53 | const headCloseMatch = content.indexOf("</head>"); |
| 54 | if (headCloseMatch === -1) { |
| 55 | console.log(`⚠️ Warning: Could not find insertion point in ${filePath}`); |
| 56 | return; |
| 57 | } |
| 58 | insertPosition = headCloseMatch; |
| 59 | } |
| 60 | |
| 61 | // Inject the og:image and twitter:image meta tags |
| 62 | const ogImageTag = `<meta property="og:image" content="${ogImageUrl}"/>`; |
| 63 | const twitterImageTag = `<meta name="twitter:image" content="${ogImageUrl}"/>`; |
| 64 | const newContent = |
| 65 | content.slice(0, insertPosition) + |
| 66 | ogImageTag + |
| 67 | twitterImageTag + |
| 68 | content.slice(insertPosition); |
| 69 | |
| 70 | // Write the modified content back to the file |
| 71 | await Bun.write(filePath, newContent); |
| 72 | console.log(`✅ Injected og:image tags into ${filePath}`); |
| 73 | } |
| 74 | |
| 75 | // Main execution |
| 76 | async function main() { |
| 77 | console.log("🔍 Finding HTML files in dist directory..."); |
| 78 | const htmlFiles = findHtmlFiles(distDir); |
| 79 | console.log(`📄 Found ${htmlFiles.length} HTML files`); |
| 80 | |
| 81 | for (const file of htmlFiles) { |
| 82 | await injectOgImageTags(file); |
| 83 | } |
| 84 | |
| 85 | console.log("\n✨ Done! All HTML files have been processed."); |
| 86 | } |
| 87 | |
| 88 | main(); |