const fs = require("fs"); const path = require("path"); const config = require("./site-config.js"); const articles = JSON.parse(fs.readFileSync("./data/articles.json", "utf-8")); const pagesData = JSON.parse(fs.readFileSync("./data/pages-data.json", "utf-8")); const homeTemplate = fs.readFileSync("./templates/index-template.html", "utf-8"); const articleTemplate = fs.readFileSync("./templates/article-template.html", "utf-8"); const aboutTemplate = fs.readFileSync("./about/about.html", "utf-8"); const privacyTemplate = fs.readFileSync("./privacy-policy/privacy.html", "utf-8"); const distDir = "./dist"; const articleOut = path.join(distDir, "articles"); /* ========================================================= FRESH DEPLOYMENT: CLEAR OUTPUT ========================================================= */ if (fs.existsSync(distDir)) { fs.rmSync(distDir, { recursive: true, force: true }); console.log("๐Ÿงน /dist cleared for fresh deployment."); } fs.mkdirSync(articleOut, { recursive: true }); /* ========================================================= HELPERS ========================================================= */ function formatSections(sections) { return (sections || []) .map(s => `

${s.heading || s.title}

${s.text || s.body}

`) .join(""); } function scoreArticle(article) { let score = 0; const rawDate = article.datePublished || article.created_at; const parsedDate = new Date(rawDate); const safeTime = !isNaN(parsedDate.getTime()) ? parsedDate.getTime() : Date.now(); const daysOld = (Date.now() - safeTime) / (1000 * 60 * 60 * 24); score += Math.max(0, 10 - daysOld); if (article.image) score += 3; if (article.trendingBoost) score += article.trendingBoost; return score; } function getRelatedArticles(current, all) { return all .filter(a => a.slug !== current.slug) .map(a => { let relScore = 0; if (a.category === current.category) relScore += 10; return { ...a, relScore }; }) .sort((a, b) => { const timeA = new Date(a.datePublished || a.created_at).getTime(); const timeB = new Date(b.datePublished || b.created_at).getTime(); return b.relScore - a.relScore || timeB - timeA; }) .slice(0, 4) .map(r => ` `).join(""); } /* ========================================================= RANK ARTICLES ========================================================= */ const ranked = articles .map(article => ({ ...article, score: scoreArticle(article) })) .sort((a, b) => b.score - a.score); /* ========================================================= BUILD ARTICLES ========================================================= */ ranked.forEach((article) => { const canonical = `${config.baseUrl}/articles/${article.slug}.html`; const relatedHTML = getRelatedArticles(article, ranked); const parsedDate = new Date(article.datePublished || article.created_at); const safeDate = isNaN(parsedDate.getTime()) ? new Date() : parsedDate; // Hardcode the call-to-action text arrays uniformly across all pages const structuredIngredients = [ "Visit site to see list of ingredients" ]; const structuredInstructions = [ { "@type": "HowToStep", "text": "Visit site for instructions" } ]; // Dynamic Recipe Configuration Settings const recipeYield = article.recipeYield || "4 servings"; const cookTime = article.cookTime || "PT15M"; const totalTime = article.totalTime || "PT20M"; // Dynamic Aggregate Ratings Fallbacks const ratingValue = article.ratingValue || "4.9"; const reviewCount = article.reviewCount || "124"; let html = articleTemplate .replace(/__TITLE__/g, article.title) .replace(/__DESCRIPTION__/g, article.description || article.title) .replace(/__IMAGE_URL__/g, article.image || "") .replace(/__CONTENT__/g, article.content) .replace(/__INTRO_PARAGRAPH__/g, article.intro || "") .replace(/__DATE_PUBLISHED__/g, article.datePublished || safeDate.toISOString()) .replace(/__DATE_MODIFIED__/g, article.dateModified || article.datePublished || safeDate.toISOString()) .replace(/__DISPLAY_DATE__/g, safeDate.toDateString()) .replace(/__CANONICAL_URL__/g, canonical) .replace(/__RELATED_ARTICLES_DYNAMIC__/g, relatedHTML) .replace(/__RECIPE_YIELD__/g, recipeYield) .replace(/__COOK_TIME__/g, cookTime) .replace(/__TOTAL_TIME__/g, totalTime) .replace(/__RATING_VALUE__/g, ratingValue) .replace(/__REVIEW_COUNT__/g, reviewCount) .replace(/__INGREDIENTS_JSON_ARRAY__/g, JSON.stringify(structuredIngredients)) .replace(/__INSTRUCTIONS_JSON_ARRAY__/g, JSON.stringify(structuredInstructions)); fs.writeFileSync(path.join(articleOut, `${article.slug}.html`), html); }); /* ========================================================= HOMEPAGE ========================================================= */ const latest = ranked[0]; const grid = ranked.slice(1).map(article => `
${article.title}

${article.title}

${article.description || article.title}

`).join(""); const homepage = homeTemplate .replace(/{{LATEST_URL}}/g, `articles/${latest.slug}.html`) .replace(/{{LATEST_IMAGE}}/g, latest.image) .replace(/{{LATEST_TITLE}}/g, latest.title) .replace(/{{LATEST_DESCRIPTION}}/g, latest.description || latest.title) .replace(/{{ALL_ARTICLES_GRID}}/g, grid); fs.writeFileSync(path.join(distDir, "index.html"), homepage); /* ========================================================= STATIC PAGES ========================================================= */ const finalAbout = aboutTemplate .replace(/__TITLE__/g, pagesData.pages.about.title) .replace(/__DESCRIPTION__/g, pagesData.pages.about.description) .replace(/__CANONICAL_URL__/g, pagesData.pages.about.canonical) .replace(/__CONTENT__/g, formatSections(pagesData.pages.about.content_sections)); fs.writeFileSync(path.join(distDir, "about.html"), finalAbout); const finalPrivacy = privacyTemplate .replace(/__TITLE__/g, pagesData.pages.privacy.title) .replace(/__DESCRIPTION__/g, pagesData.pages.privacy.description) .replace(/__CANONICAL_URL__/g, pagesData.pages.privacy.canonical) .replace(/__DATE_MODIFIED__/g, pagesData.pages.privacy.dateModified || new Date().toDateString()) .replace(/__CONTENT__/g, formatSections(pagesData.pages.privacy.sections)); fs.writeFileSync(path.join(distDir, "privacy.html"), finalPrivacy); if (fs.existsSync("./contact-us.html")) { fs.copyFileSync("./contact-us.html", path.join(distDir, "contact-us.html")); } /* ========================================================= STATIC ROOT DEPLOYMENT & ASSET COPIES ========================================================= */ // Deploy Ultimate Cajun Potato Soup if (fs.existsSync("./ultimate-cajun-potato-soup.html")) { fs.copyFileSync("./ultimate-cajun-potato-soup.html", path.join(distDir, "ultimate-cajun-potato-soup.html")); } // Deploy Cristiano Ronaldo vs Messi GOAT Debate if (fs.existsSync("./cristiano-ronaldo-vs-messi-goat-debate.html")) { fs.copyFileSync("./cristiano-ronaldo-vs-messi-goat-debate.html", path.join(distDir, "cristiano-ronaldo-vs-messi-goat-debate.html")); } // Deploy Mbappe Next Generation Messi New Era if (fs.existsSync("./mbappe-next-generation-messi-new-era.html")) { fs.copyFileSync("./mbappe-next-generation-messi-new-era.html", path.join(distDir, "mbappe-next-generation-messi-new-era.html")); } // Deploy IMG_3389.png alongside static files in the root /dist if (fs.existsSync("./IMG_3389.png")) { fs.copyFileSync("./IMG_3389.png", path.join(distDir, "IMG_3389.png")); console.log("๐Ÿ–ผ๏ธ Deploying 'IMG_3389.png' to /dist."); } /* ========================================================= RSS + SITEMAP GENERATION ========================================================= */ const rssItems = ranked.slice(0, 50).map(article => { const d = new Date(article.datePublished || article.created_at); const safe = isNaN(d.getTime()) ? new Date() : d; const itemUrl = `${config.baseUrl}/articles/${article.slug}.html`; return ` <![CDATA[${article.title}]]> ${itemUrl} ${itemUrl} ${safe.toUTCString()} `; }).join(""); const rssFeed = ` ${config.siteName} ${config.baseUrl} Latest news updates from ${config.siteName} en-ng ${new Date().toUTCString()} ${rssItems} `; fs.writeFileSync(path.join(distDir, "rss.xml"), rssFeed); // Add both static html files explicitly to the static sitemap section const sitemapStaticXml = ["about.html", "privacy.html", "contact-us.html", "ultimate-cajun-potato-soup.html", "cristiano-ronaldo-vs-messi-goat-debate.html", "mbappe-next-generation-messi-new-era.html"] .map(page => ` ${config.baseUrl}/${page} `).join(""); const sitemapArticlesXml = ranked.map(a => ` ${config.baseUrl}/articles/${a.slug}.html ${(a.datePublished || new Date().toISOString()).split('T')[0]} `).join(""); const sitemap = ` ${config.baseUrl}/ ${sitemapStaticXml}${sitemapArticlesXml} `; fs.writeFileSync(path.join(distDir, "sitemap.xml"), sitemap); /* ========================================================= ASSETS + ROBOTS ========================================================= */ function copyFolder(src, dest) { if (!fs.existsSync(src)) return; fs.mkdirSync(dest, { recursive: true }); fs.readdirSync(src, { withFileTypes: true }).forEach(entry => { const s = path.join(src, entry.name); const d = path.join(dest, entry.name); entry.isDirectory() ? copyFolder(s, d) : fs.copyFileSync(s, d); }); } copyFolder("./assets", path.join(distDir, "assets")); if (fs.existsSync("./logo.PNG")) { fs.copyFileSync("./logo.PNG", path.join(distDir, "logo.PNG")); } else if (fs.existsSync("./logo.png")) { fs.copyFileSync("./logo.png", path.join(distDir, "logo.png")); } fs.writeFileSync( path.join(distDir, "robots.txt"), `User-agent: *\nAllow: /\n\nSitemap: ${config.baseUrl}/sitemap.xml` ); console.log("โœ… BUILD COMPLETE โ€” SITE GENERATED SUCCESSFULLY"); /* ========================================================= AUTOMATED GOOGLE INDEXING DISPATCH PIPELINE ========================================================= */ const WORKER_ENDPOINT = "https://google-fast-indexer.winnifredjoo.workers.dev/?secret=Kamikaye0@"; async function runGoogleIndexerPipeline() { if (!ranked || ranked.length === 0) { console.log("โš ๏ธ Indexer aborted: No compiled post records found."); return; } const latestPost = ranked[0]; const targetedProductionUrl = `${config.baseUrl}/articles/${latestPost.slug}.html`; console.log(`๐Ÿš€ Dispatching fresh article context to Cloudflare Worker: ${targetedProductionUrl}`); try { const res = await fetch(WORKER_ENDPOINT, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ url: targetedProductionUrl, event: "updated" }) }); const statusData = await res.json(); console.log("๐Ÿค– Cloudflare Worker Handshake Receipt:", JSON.stringify(statusData)); } catch (err) { console.error("โŒ Google index transaction connection execution interrupted:", err.message); } } runGoogleIndexerPipeline();