diff --git a/index.html b/index.html
index 04ba61a..cf53111 100644
--- a/index.html
+++ b/index.html
@@ -198,6 +198,9 @@
+
+
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 @@
+
+
Run the example with Docker: