From ae415d9093c61314fa9f2ba317a412043c068446 Mon Sep 17 00:00:00 2001 From: Ray Clanan <115944+rclanan@users.noreply.github.com> Date: Mon, 12 Oct 2026 10:54:20 -0400 Subject: [PATCH] fix(marketing): generate canonical sitemap and show GitHub stars --- compare-mockserver.html | 1 + compare-postman-mock.html | 1 + compare-wiremock.html | 1 + contact.html | 1 + engineering-notes.html | 3 +- index.html | 3 + privacy.html | 1 + public/sitemap.xml | 238 +++++++------------------------------- scripts/build-pages.js | 37 +++++- src/pages/index.html | 3 + 10 files changed, 92 insertions(+), 197 deletions(-) diff --git a/compare-mockserver.html b/compare-mockserver.html index e6fcb13..4731925 100644 --- a/compare-mockserver.html +++ b/compare-mockserver.html @@ -127,6 +127,7 @@ } } +
diff --git a/compare-postman-mock.html b/compare-postman-mock.html index 6501208..b82e04a 100644 --- a/compare-postman-mock.html +++ b/compare-postman-mock.html @@ -127,6 +127,7 @@ } } +
diff --git a/compare-wiremock.html b/compare-wiremock.html index 431af71..fa2d997 100644 --- a/compare-wiremock.html +++ b/compare-wiremock.html @@ -127,6 +127,7 @@ } } +
diff --git a/contact.html b/contact.html index ed094cf..0470d49 100644 --- a/contact.html +++ b/contact.html @@ -115,6 +115,7 @@ } } +
diff --git a/engineering-notes.html b/engineering-notes.html index 2a29254..0bdb913 100644 --- a/engineering-notes.html +++ b/engineering-notes.html @@ -108,7 +108,8 @@ } } - + +
diff --git a/index.html b/index.html index 04ba61a..cf53111 100644 --- a/index.html +++ b/index.html @@ -198,6 +198,9 @@

+ MockForge GitHub stars +

Run the example with Docker:

diff --git a/privacy.html b/privacy.html index 2b79e47..2f686e4 100644 --- a/privacy.html +++ b/privacy.html @@ -115,6 +115,7 @@ } } +
diff --git a/public/sitemap.xml b/public/sitemap.xml index c6d84c3..6f68bbd 100644 --- a/public/sitemap.xml +++ b/public/sitemap.xml @@ -1,198 +1,46 @@ - - https://mockforge.dev/ - weekly - 1.0 - - - https://mockforge.dev/pricing.html - monthly - 0.9 - - - https://mockforge.dev/engineering-notes.html - weekly - 0.8 - - - https://mockforge.dev/compare-wiremock.html - monthly - 0.7 - - - https://mockforge.dev/compare-mockserver.html - monthly - 0.7 - - - https://mockforge.dev/compare-postman-mock.html - monthly - 0.7 - - - https://mockforge.dev/note-cloud-launch.html - monthly - 0.9 - - - https://mockforge.dev/note-grpc-mocking-in-ci.html - monthly - 0.8 - - - https://mockforge.dev/note-why-multi-protocol.html - monthly - 0.7 - - - https://mockforge.dev/note-mocking-kafka-without-broker.html - monthly - 0.8 - - - https://mockforge.dev/note-when-wiremock-isnt-enough.html - monthly - 0.7 - - - https://mockforge.dev/note-cost-cap-design.html - monthly - 0.6 - - - https://mockforge.dev/note-our-saas-infrastructure.html - monthly - 0.7 - - - https://mockforge.dev/note-announcing-v1.html - monthly - 0.6 - - - https://mockforge.dev/note-api-drift-detection-and-contract-alerting.html - monthly - 0.5 - - - https://mockforge.dev/note-client-generation-tradeoffs.html - monthly - 0.5 - - - https://mockforge.dev/note-conformance-testing.html - monthly - 0.5 - - - https://mockforge.dev/note-migration-from-static-fixtures.html - monthly - 0.5 - - - https://mockforge.dev/note-multi-target-conformance-and-real-world-specs.html - monthly - 0.5 - - - https://mockforge.dev/note-release-cadence-and-risk.html - monthly - 0.5 - - - https://mockforge.dev/note-reliability-update-march-2026.html - monthly - 0.5 - - - https://mockforge.dev/note-reliability-updates-conformance-and-admin.html - monthly - 0.5 - - - https://mockforge.dev/note-scenario-switching.html - monthly - 0.5 - - - https://mockforge.dev/note-security-guardrails-v0-2x.html - monthly - 0.5 - - - https://mockforge.dev/note-spec-driven-openapi-conformance.html - monthly - 0.5 - - - https://mockforge.dev/note-testing-failure-paths.html - monthly - 0.5 - - - https://mockforge.dev/note-v0-1-3-protocol-expansion.html - monthly - 0.5 - - - https://mockforge.dev/note-v0-2-0-client-generation.html - monthly - 0.5 - - - https://mockforge.dev/note-v0-2-3-bug-fixes.html - monthly - 0.5 - - - https://mockforge.dev/note-v0-2-4-client-fixes.html - monthly - 0.5 - - - https://mockforge.dev/note-v0-2-5-enterprise-security.html - monthly - 0.5 - - - https://mockforge.dev/note-v0-2-6-security-infrastructure.html - monthly - 0.5 - - - https://mockforge.dev/note-v0-2-7-api-sync-tcp.html - monthly - 0.5 - - - https://mockforge.dev/note-v0-3-migration-playbook.html - monthly - 0.5 - - - https://mockforge.dev/note-wafbench-security-hardening.html - monthly - 0.5 - - - https://mockforge.dev/note-wafbench-sqli-detection.html - monthly - 0.5 - - - https://mockforge.dev/contact.html - yearly - 0.3 - - - https://mockforge.dev/privacy.html - yearly - 0.3 - - - https://mockforge.dev/trust.html - monthly - 0.5 - + https://mockforge.dev/ + https://mockforge.dev/compare-mockserver.html + https://mockforge.dev/compare-postman-mock.html + https://mockforge.dev/compare-wiremock.html + https://mockforge.dev/engineering-notes.html + https://mockforge.dev/note-announcing-v1.html + https://mockforge.dev/note-api-drift-detection-and-contract-alerting.html + https://mockforge.dev/note-client-generation-tradeoffs.html + https://mockforge.dev/note-cloud-launch.html + https://mockforge.dev/note-conformance-maturity.html + https://mockforge.dev/note-conformance-testing.html + https://mockforge.dev/note-cost-cap-design.html + https://mockforge.dev/note-fault-injection-patterns.html + https://mockforge.dev/note-grpc-dynamic-routing-release.html + https://mockforge.dev/note-grpc-mocking-in-ci.html + https://mockforge.dev/note-migration-from-static-fixtures.html + https://mockforge.dev/note-mocking-kafka-without-broker.html + https://mockforge.dev/note-multi-target-conformance-and-real-world-specs.html + https://mockforge.dev/note-our-saas-infrastructure.html + https://mockforge.dev/note-release-cadence-and-risk.html + https://mockforge.dev/note-reliability-update-march-2026.html + https://mockforge.dev/note-reliability-updates-conformance-and-admin.html + https://mockforge.dev/note-scenario-switching.html + https://mockforge.dev/note-security-guardrails-v0-2x.html + https://mockforge.dev/note-spec-driven-openapi-conformance.html + https://mockforge.dev/note-testing-failure-paths.html + https://mockforge.dev/note-v0-1-3-protocol-expansion.html + https://mockforge.dev/note-v0-2-0-client-generation.html + https://mockforge.dev/note-v0-2-3-bug-fixes.html + https://mockforge.dev/note-v0-2-4-client-fixes.html + https://mockforge.dev/note-v0-2-5-enterprise-security.html + https://mockforge.dev/note-v0-2-6-security-infrastructure.html + https://mockforge.dev/note-v0-2-7-api-sync-tcp.html + https://mockforge.dev/note-v0-3-227-release.html + https://mockforge.dev/note-v0-3-migration-playbook.html + https://mockforge.dev/note-vs-postman-mock.html + https://mockforge.dev/note-wafbench-security-hardening.html + https://mockforge.dev/note-wafbench-sqli-detection.html + https://mockforge.dev/note-wasm-plugins.html + https://mockforge.dev/note-when-wiremock-isnt-enough.html + https://mockforge.dev/note-why-multi-protocol.html + https://mockforge.dev/pricing.html + https://mockforge.dev/trust.html diff --git a/scripts/build-pages.js b/scripts/build-pages.js index e354cef..71ed26d 100644 --- a/scripts/build-pages.js +++ b/scripts/build-pages.js @@ -569,6 +569,9 @@ function emit(file, text) { } } +const sitemapUrls = new Set(); +const siteOrigin = 'https://mockforge.dev'; + for (const file of pageFiles) { const config = getPageConfig(file); let text = fs.readFileSync(path.join(sourceRoot, file), 'utf8'); @@ -597,9 +600,38 @@ for (const file of pageFiles) { } } + // Indexable pages use their actual canonical URL; redirects and the 404 + // page are excluded. Generating this during each build keeps new notes + // discoverable without maintaining a second hand-written page list. + if (file !== '404.html') { + const canonicalTags = [...text.matchAll(/]*\brel=["']canonical["'])[^>]*\bhref=["']([^"']+)["'][^>]*>/gi)]; + if (canonicalTags.length > 1) { + throw new Error(`Multiple canonical URLs in ${file}`); + } + const canonical = canonicalTags.length + ? canonicalTags[0][1] + : `${siteOrigin}/${file === 'index.html' ? '' : file}`; + const url = new URL(canonical); + if (url.origin !== siteOrigin || url.search || url.hash) { + throw new Error(`Invalid canonical URL in ${file}`); + } + if (canonicalTags.length === 0) { + text = text.replace('', ` \n `); + } + const noindex = /]*\bname=["'](?:robots|googlebot)["'])[^>]*\bcontent=["'][^"']*\bnoindex\b/gi.test(text); + if (!noindex) sitemapUrls.add(url.href); + } + emit(file, text); } +const sitemap = ` + +${[...sitemapUrls].sort().map(url => ` ${escapeXml(url)}`).join('\n')} + +`; +emit('public/sitemap.xml', sitemap); + for (const [file, redirect] of Object.entries(REDIRECTS)) { if (pageFiles.includes(file)) { throw new Error(`Redirect ${file} collides with src/pages/${file}`); @@ -619,10 +651,13 @@ if (outputRoot !== projectRoot && !flagCheck) { fs.copyFileSync(cnamePath, path.join(outputRoot, 'CNAME')); } + fs.writeFileSync(path.join(outputRoot, 'public', 'sitemap.xml'), sitemap); + fs.writeFileSync(path.join(outputRoot, 'sitemap.xml'), sitemap); + // Serve agent/SEO discovery files from the dist root, not only under // /public/. Scanners and agents expect /robots.txt, /sitemap.xml, // /llms.txt, /openapi.json, and /openapi.registry.json at the domain root. - for (const rootAsset of ['robots.txt', 'sitemap.xml', 'llms.txt', 'openapi.json', 'openapi.registry.json']) { + for (const rootAsset of ['robots.txt', 'llms.txt', 'openapi.json', 'openapi.registry.json']) { const assetPath = path.join(projectRoot, 'public', rootAsset); if (fs.existsSync(assetPath)) { fs.copyFileSync(assetPath, path.join(outputRoot, rootAsset)); diff --git a/src/pages/index.html b/src/pages/index.html index 0b70cf9..7ef588a 100644 --- a/src/pages/index.html +++ b/src/pages/index.html @@ -109,6 +109,9 @@

+ MockForge GitHub stars +

Run the example with Docker: