syntaxai/tdd.md · main · src / b32_sitemap.ts
// b32 — Layer 1 pure helper: render a sitemaps.org 0.9 urlset.
// No I/O. Deterministic: same input array → same output bytes.
// Caller (d21_app.ts) composes the URL list from ALL_POSTS,
// ALL_SAMA, ALL_GUIDES, and STATIC_PATHS, then asks this module
// for the XML string. Sibling test pins escape behaviour and
// shape; the verifier's §4.3 modeled-tests check requires it.
export interface SitemapUrl {
readonly loc: string;
readonly lastmod?: string;
}
// The eleven load-bearing static URLs that don't come from a
// registry. Each must correspond to a literal route registered
// in d21_app.ts; the handler iterates this list verbatim.
export const STATIC_PATHS: ReadonlyArray<string> = [
"/",
"/blog",
"/contributing",
"/games",
"/goals",
"/leaderboard",
"/sama",
"/sama/v2",
"/sama/v2/verify",
"/sama/v2/example-crud",
"/sama/v2/example-wordpress",
"/sama/skill",
"/guides",
];
// Minimal XML 1.0 escape for character data + attribute values.
// The five named entities are the canonical set; anything else
// is left as-is (the sitemap spec requires UTF-8, not ASCII).
export const escapeXml = (s: string): string =>
s
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
const renderUrl = (u: SitemapUrl): string => {
const loc = `<loc>${escapeXml(u.loc)}</loc>`;
const lastmod =
u.lastmod !== undefined ? `<lastmod>${escapeXml(u.lastmod)}</lastmod>` : "";
return ` <url>${loc}${lastmod}</url>`;
};
export const renderSitemap = (urls: ReadonlyArray<SitemapUrl>): string => {
const body = urls.map(renderUrl).join("\n");
const inner = body.length > 0 ? `\n${body}\n` : "\n";
return `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">${inner}</urlset>`;
};