import fs from "node:fs/promises"; import path, { dirname } from "node:path"; import { fileURLToPath } from "node:url"; import { XMLParser } from "fast-xml-parser"; import { z } from "zod/v4"; import { createDocumentationLink } from "./index"; import { integrationDocSlugs } from "./integration-doc-slugs"; import { widgetDocSlugs } from "./widget-doc-slugs"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const removeCommonUrl = (url: string) => { return url.replace("https://homarr.dev", ""); }; const sitemapSchema = z.object({ urlset: z.object({ url: z.array( z.object({ loc: z.string(), }), ), }), }); const fetchSitemapAsync = async () => { const response = await fetch(createDocumentationLink("/sitemap.xml")); return await response.text(); }; const parseXml = (sitemapXml: string) => { const parser = new XMLParser(); const data: unknown = parser.parse(sitemapXml); const result = sitemapSchema.safeParse(data); if (!result.success) { throw new Error("Invalid sitemap schema"); } return result.data; }; const mapSitemapXmlToPaths = (sitemapData: z.infer) => { return sitemapData.urlset.url.map((url) => removeCommonUrl(url.loc)); }; const createSitemapPathType = (paths: string[]) => { return "export type HomarrDocumentationPath =\n" + paths.map((path) => ` | "${path.replace(/\/$/, "")}"`).join("\n"); }; const updateSitemapTypeFileAsync = async (sitemapPathType: string) => { const content = "// This file is auto-generated by the codegen script\n" + "// it uses the sitemap.xml to generate the HomarrDocumentationPath type\n" + sitemapPathType + ";\n"; await fs.writeFile(outputPath, content); }; const slugMapPaths = [ ...Object.values(integrationDocSlugs) .filter(Boolean) .map((slug) => `/docs/integrations/${slug}`), ...Object.values(widgetDocSlugs) .filter(Boolean) .map((slug) => `/docs/widgets/${slug}`), ]; const outputPath = path.join(__dirname, "homarr-docs-sitemap.ts"); const main = async () => { let paths: string[]; try { const sitemapXml = await fetchSitemapAsync(); const sitemapData = parseXml(sitemapXml); paths = mapSitemapXmlToPaths(sitemapData); } catch { const exists = await fs.access(outputPath).then( () => true, () => false, ); if (exists) { console.warn("Could not fetch sitemap from homarr.dev, keeping existing generated file"); return; } console.warn("Could not fetch sitemap from homarr.dev and no existing file, generating from slug maps only"); paths = []; } paths.push("/sitemap.xml"); for (const p of slugMapPaths) { if (!paths.includes(p)) { paths.push(p); } } const sitemapPathType = createSitemapPathType(paths); await updateSitemapTypeFileAsync(sitemapPathType); }; void main();