{"version":3,"sources":["../src/validate.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * @lacspace/sitemap — validation & formatting helpers.\n *\n * Small, zero-dependency utilities for keeping sitemaps spec-compliant:\n * clamp `priority`, validate `changefreq`, format `lastmod` as W3C/ISO, and\n * guard against the 50,000-URL-per-file limit with a clear error.\n *\n * All additive — the core builders (`sitemap`, `splitSitemaps`, …) already\n * clamp priority and format dates internally; these expose the same rules so\n * callers can validate their data before building.\n */\n\nimport type { ChangeFreq } from \"./index\";\n\n/** Max URLs a single sitemap file may contain (sitemaps.org spec). */\nexport const SITEMAP_MAX_URLS = 50000;\n\n/** The seven valid `<changefreq>` values, in spec order. */\nexport const CHANGEFREQS: readonly ChangeFreq[] = [\n  \"always\",\n  \"hourly\",\n  \"daily\",\n  \"weekly\",\n  \"monthly\",\n  \"yearly\",\n  \"never\",\n];\n\n/** Type guard: is `value` one of the seven valid `<changefreq>` values? */\nexport function isValidChangefreq(value: unknown): value is ChangeFreq {\n  return typeof value === \"string\" && (CHANGEFREQS as readonly string[]).includes(value);\n}\n\n/**\n * Clamp a `priority` into the valid 0.0–1.0 range and round to one decimal\n * (matching the value the builders actually emit). Non-finite input (`NaN`,\n * `Infinity`) falls back to the sitemap default of `0.5`.\n * @example clampPriority(1.7) // 1\n * @example clampPriority(-3)  // 0\n * @example clampPriority(0.83) // 0.8\n */\nexport function clampPriority(priority: number): number {\n  if (!Number.isFinite(priority)) return 0.5;\n  const clamped = Math.max(0, Math.min(1, priority));\n  return Math.round(clamped * 10) / 10;\n}\n\n/**\n * Format a `lastmod` value as a W3C Datetime string (ISO 8601).\n * A `Date` becomes its full `toISOString()`; a string is passed through\n * unchanged (assumed already valid). Invalid `Date`s throw a clear error.\n * @example formatLastmod(new Date(\"2026-09-05T00:00:00Z\")) // \"2026-09-05T00:00:00.000Z\"\n */\nexport function formatLastmod(date: Date | string): string {\n  if (date instanceof Date) {\n    if (Number.isNaN(date.getTime())) {\n      throw new RangeError(\"[@lacspace/sitemap] lastmod is an Invalid Date\");\n    }\n    return date.toISOString();\n  }\n  return date;\n}\n\n/**\n * Throw a clear `RangeError` if `count` exceeds the per-file limit. Use before\n * writing a single sitemap file; for larger sets use {@link splitSitemaps}.\n * @param count number of URLs\n * @param cap max allowed (default {@link SITEMAP_MAX_URLS}); values above the\n *   spec max are themselves capped at 50,000.\n */\nexport function assertUrlCount(count: number, cap: number = SITEMAP_MAX_URLS): void {\n  const limit = Math.min(cap, SITEMAP_MAX_URLS);\n  if (count > limit) {\n    throw new RangeError(\n      `[@lacspace/sitemap] ${count} URLs exceeds the limit of ${limit} per sitemap file. ` +\n        `Use splitSitemaps() to shard into a sitemap index.`,\n    );\n  }\n}\n\n/** A single validation problem found on a URL entry. */\nexport interface SitemapUrlIssue {\n  /** The offending field. */\n  field: \"loc\" | \"priority\" | \"changefreq\" | \"lastmod\";\n  /** Human-readable description. */\n  message: string;\n}\n\n/**\n * Non-throwing validation of one URL-like entry. Returns a list of issues\n * (empty = valid). Checks that `loc` is a non-empty absolute URL, `priority`\n * is within 0.0–1.0, `changefreq` is a valid enum value, and `lastmod` parses.\n * @example validateSitemapUrl({ loc: \"/rel\", priority: 2 }) // 2 issues\n */\nexport function validateSitemapUrl(u: {\n  loc?: unknown;\n  priority?: unknown;\n  changefreq?: unknown;\n  lastmod?: unknown;\n}): SitemapUrlIssue[] {\n  const issues: SitemapUrlIssue[] = [];\n  if (typeof u.loc !== \"string\" || u.loc.length === 0) {\n    issues.push({ field: \"loc\", message: \"loc is required and must be a non-empty string\" });\n  } else if (!/^https?:\\/\\//i.test(u.loc)) {\n    issues.push({ field: \"loc\", message: `loc must be an absolute http(s) URL: \"${u.loc}\"` });\n  }\n  if (u.priority !== undefined) {\n    if (typeof u.priority !== \"number\" || !Number.isFinite(u.priority)) {\n      issues.push({ field: \"priority\", message: \"priority must be a finite number\" });\n    } else if (u.priority < 0 || u.priority > 1) {\n      issues.push({ field: \"priority\", message: `priority ${u.priority} is outside 0.0–1.0` });\n    }\n  }\n  if (u.changefreq !== undefined && !isValidChangefreq(u.changefreq)) {\n    issues.push({\n      field: \"changefreq\",\n      message: `changefreq \"${String(u.changefreq)}\" is not one of ${CHANGEFREQS.join(\", \")}`,\n    });\n  }\n  if (u.lastmod instanceof Date && Number.isNaN(u.lastmod.getTime())) {\n    issues.push({ field: \"lastmod\", message: \"lastmod is an Invalid Date\" });\n  }\n  return issues;\n}\n","/**\n * @lacspace/sitemap\n * Generate sitemap.xml, sitemap indexes and Next.js sitemaps.\n *\n * URL entries with lastmod / changefreq / priority, image / video / news\n * extensions and hreflang alternates. Auto-splits large sets into an index.\n *\n * Zero dependencies · isomorphic · fully typed.\n */\n\nimport { assertUrlCount, clampPriority, formatLastmod, isValidChangefreq } from \"./validate\";\n\nexport {\n  SITEMAP_MAX_URLS,\n  CHANGEFREQS,\n  isValidChangefreq,\n  clampPriority,\n  formatLastmod,\n  assertUrlCount,\n  validateSitemapUrl,\n} from \"./validate\";\nexport type { SitemapUrlIssue } from \"./validate\";\n\nexport type ChangeFreq =\n  | \"always\"\n  | \"hourly\"\n  | \"daily\"\n  | \"weekly\"\n  | \"monthly\"\n  | \"yearly\"\n  | \"never\";\n\nexport interface SitemapImage {\n  loc: string;\n  title?: string;\n  caption?: string;\n}\n\nexport interface SitemapVideo {\n  thumbnailLoc: string;\n  title: string;\n  description: string;\n  contentLoc?: string;\n  playerLoc?: string;\n  duration?: number;\n}\n\nexport interface SitemapNews {\n  publicationName: string;\n  language: string;\n  title: string;\n  publicationDate: string | Date;\n}\n\nexport interface Alternate {\n  hreflang: string;\n  href: string;\n}\n\nexport interface SitemapUrl {\n  loc: string;\n  lastmod?: string | Date;\n  changefreq?: ChangeFreq;\n  /** 0.0–1.0 */\n  priority?: number;\n  images?: SitemapImage[];\n  videos?: SitemapVideo[];\n  news?: SitemapNews;\n  alternates?: Alternate[];\n}\n\nfunction esc(s: string): string {\n  return s\n    .replace(/[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F]/g, \"\")\n    .replace(/&/g, \"&amp;\")\n    .replace(/</g, \"&lt;\")\n    .replace(/>/g, \"&gt;\")\n    .replace(/\"/g, \"&quot;\")\n    .replace(/'/g, \"&apos;\");\n}\n\nfunction w3c(date: string | Date): string {\n  // Delegate so an Invalid Date reports which field and package is at fault,\n  // instead of V8's bare \"Invalid time value\".\n  return formatLastmod(date);\n}\n\nfunction urlBlock(u: SitemapUrl): string {\n  const parts = [`    <loc>${esc(u.loc)}</loc>`];\n  if (u.lastmod) parts.push(`    <lastmod>${esc(w3c(u.lastmod))}</lastmod>`);\n  // Only the seven spec values are valid here. Emitting anything else made the\n  // whole file fail validation, so an unrecognised value is dropped: the URL is\n  // still listed, just without a crawl-frequency hint.\n  if (u.changefreq && isValidChangefreq(u.changefreq)) {\n    parts.push(`    <changefreq>${u.changefreq}</changefreq>`);\n  }\n  if (u.priority !== undefined) {\n    const p = Math.max(0, Math.min(1, u.priority));\n    parts.push(`    <priority>${p.toFixed(1)}</priority>`);\n  }\n  for (const a of u.alternates ?? []) {\n    parts.push(\n      `    <xhtml:link rel=\"alternate\" hreflang=\"${esc(a.hreflang)}\" href=\"${esc(a.href)}\" />`,\n    );\n  }\n  for (const img of u.images ?? []) {\n    parts.push(`    <image:image><image:loc>${esc(img.loc)}</image:loc>${\n      img.title ? `<image:title>${esc(img.title)}</image:title>` : \"\"\n    }${img.caption ? `<image:caption>${esc(img.caption)}</image:caption>` : \"\"}</image:image>`);\n  }\n  for (const v of u.videos ?? []) {\n    parts.push(\n      `    <video:video>` +\n        `<video:thumbnail_loc>${esc(v.thumbnailLoc)}</video:thumbnail_loc>` +\n        `<video:title>${esc(v.title)}</video:title>` +\n        `<video:description>${esc(v.description)}</video:description>` +\n        (v.contentLoc ? `<video:content_loc>${esc(v.contentLoc)}</video:content_loc>` : \"\") +\n        (v.playerLoc ? `<video:player_loc>${esc(v.playerLoc)}</video:player_loc>` : \"\") +\n        (v.duration ? `<video:duration>${v.duration}</video:duration>` : \"\") +\n        `</video:video>`,\n    );\n  }\n  if (u.news) {\n    parts.push(\n      `    <news:news><news:publication><news:name>${esc(u.news.publicationName)}</news:name>` +\n        `<news:language>${esc(u.news.language)}</news:language></news:publication>` +\n        `<news:publication_date>${esc(w3c(u.news.publicationDate))}</news:publication_date>` +\n        `<news:title>${esc(u.news.title)}</news:title></news:news>`,\n    );\n  }\n  return `  <url>\\n${parts.join(\"\\n\")}\\n  </url>`;\n}\n\nconst NS =\n  'xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\" ' +\n  'xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" ' +\n  'xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" ' +\n  'xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\" ' +\n  'xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\"';\n\nexport interface SitemapOptions {\n  /**\n   * URL of an XSL stylesheet. When set, a `<?xml-stylesheet?>` processing\n   * instruction is added so the raw sitemap renders human-readably in a browser.\n   * @see {@link sitemapStylesheet}\n   */\n  stylesheet?: string;\n  /**\n   * Cap to enforce instead of the sitemaps.org max of 50,000 (values above it\n   * are themselves capped at 50,000). Use {@link splitSitemaps} for larger sets.\n   */\n  maxUrls?: number;\n  /**\n   * Skip the URL-count check entirely and emit an oversized file.\n   *\n   * The 50,000 cap is enforced by default because Google rejects the whole file\n   * past it — emitting 50,001 URLs produced an artifact no crawler would read.\n   * Only set this when the sitemap is for a consumer that has no such limit.\n   * @default false\n   */\n  allowOversize?: boolean;\n}\n\n/** Build a single sitemap.xml document. */\nexport function sitemap(urls: SitemapUrl[], opts?: SitemapOptions): string {\n  // sitemaps.org caps a single file at 50,000 URLs and Google rejects the WHOLE\n  // file past it, so this is checked by default now — silently emitting 50,001\n  // URLs produced an artifact no crawler would read. Pass `allowOversize: true`\n  // to keep the old behaviour for a non-crawler consumer.\n  if (!opts?.allowOversize) assertUrlCount(urls.length, opts?.maxUrls);\n  const pi = opts?.stylesheet\n    ? `\\n<?xml-stylesheet type=\"text/xsl\" href=\"${esc(opts.stylesheet)}\"?>`\n    : \"\";\n  const body = urls.map(urlBlock).join(\"\\n\");\n  return (\n    `<?xml version=\"1.0\" encoding=\"UTF-8\"?>${pi}\\n<urlset ${NS}>\\n` +\n    (body ? body + \"\\n\" : \"\") +\n    `</urlset>`\n  );\n}\n\n/** Build a sitemap index that points at multiple sitemaps. */\nexport function sitemapIndex(sitemaps: { loc: string; lastmod?: string | Date }[]): string {\n  const body = sitemaps\n    .map(\n      (s) =>\n        `  <sitemap>\\n    <loc>${esc(s.loc)}</loc>${\n          s.lastmod ? `\\n    <lastmod>${esc(w3c(s.lastmod))}</lastmod>` : \"\"\n        }\\n  </sitemap>`,\n    )\n    .join(\"\\n\");\n  return (\n    `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\\n` +\n    (body ? body + \"\\n\" : \"\") +\n    `</sitemapindex>`\n  );\n}\n\nexport interface SplitOptions {\n  /** Base URL where sitemap files will be hosted, e.g. \"https://x.com\". */\n  baseUrl: string;\n  /** Max URLs per file (spec max 50,000). Default 50000. */\n  perFile?: number;\n  /** Name for the i-th file (0-based). Default `sitemap-${i}.xml`. */\n  filename?: (i: number) => string;\n}\n\n/** Split a large URL set into an index + multiple sitemap files. */\nexport function splitSitemaps(\n  urls: SitemapUrl[],\n  opts: SplitOptions,\n): { index: string; files: { name: string; xml: string }[] } {\n  const per = Math.min(opts.perFile ?? 50000, 50000);\n  const filename = opts.filename ?? ((i) => `sitemap-${i}.xml`);\n  const base = opts.baseUrl.replace(/\\/$/, \"\");\n  const files: { name: string; xml: string }[] = [];\n  for (let i = 0; i * per < urls.length; i++) {\n    const name = filename(i);\n    files.push({ name, xml: sitemap(urls.slice(i * per, (i + 1) * per)) });\n  }\n  const index = sitemapIndex(files.map((f) => ({ loc: `${base}/${f.name}` })));\n  return { index, files };\n}\n\n/* ------------------------------ Next.js ------------------------------ */\n\nexport interface NextSitemapEntry {\n  url: string;\n  lastModified?: string | Date;\n  changeFrequency?: ChangeFreq;\n  priority?: number;\n  alternates?: { languages: Record<string, string> };\n}\n\n/** Convert entries to the shape Next.js `sitemap.ts` expects (`MetadataRoute.Sitemap`). */\nexport function toNextSitemap(urls: SitemapUrl[]): NextSitemapEntry[] {\n  return urls.map((u) => {\n    // Same rules as the XML path. These used to pass straight through, so the\n    // identical input produced <priority>1.0</priority> in XML but handed Next\n    // an out-of-range 1.7, and an invalid changefreq reached Next's metadata.\n    const entry: NextSitemapEntry = {\n      url: u.loc,\n      lastModified: u.lastmod,\n      changeFrequency: u.changefreq && isValidChangefreq(u.changefreq) ? u.changefreq : undefined,\n      priority: u.priority === undefined ? undefined : clampPriority(u.priority),\n    };\n    if (u.alternates?.length) {\n      entry.alternates = {\n        languages: Object.fromEntries(u.alternates.map((a) => [a.hreflang, a.href])),\n      };\n    }\n    return entry;\n  });\n}\n\n/** Minimal site shape shared across the Lacspace SEO Kit (structural, no imports). */\nexport interface SiteLike {\n  /** Absolute base URL, e.g. \"https://acme.com\". */\n  url: string;\n}\n\n/** A route: just a path string, or a full entry (its `loc`/`path` is resolved against the site URL). */\nexport type SiteRoute = string | (Omit<SitemapUrl, \"loc\"> & { loc?: string; path?: string });\n\n/**\n * Build a sitemap from bare paths — no need to repeat your domain on every row.\n * Relative locs are resolved against the site URL; absolute ones pass through.\n * @example sitemapForSite({ url: \"https://acme.com\" }, [\"/\", \"/pricing\", { path: \"/blog\", changefreq: \"daily\", priority: 0.8 }])\n */\nexport function sitemapForSite(site: SiteLike, routes: SiteRoute[]): string {\n  const base = site.url.replace(/\\/$/, \"\");\n  const resolve = (p: string): string =>\n    /^https?:\\/\\//.test(p) ? p : `${base}/${p.replace(/^\\//, \"\")}`;\n  const urls: SitemapUrl[] = routes.map((r) => {\n    if (typeof r === \"string\") return { loc: resolve(r) };\n    const { path, loc, ...rest } = r;\n    return { ...rest, loc: resolve(path ?? loc ?? \"/\") };\n  });\n  return sitemap(urls);\n}\n\n/* --------------------- Specialised extension sitemaps --------------------- */\n\n/** One entry of a Google News sitemap: a page URL plus its news metadata. */\nexport interface NewsSitemapItem extends SitemapNews {\n  loc: string;\n}\n\n/**\n * Build a Google News sitemap (`<news:news>` per URL).\n * News sitemaps should only contain articles from the last 2 days.\n * @example newsSitemap([{ loc: \"https://x.com/a\", publicationName: \"The Times\", language: \"en\", title: \"Headline\", publicationDate: new Date() }])\n */\nexport function newsSitemap(items: NewsSitemapItem[], opts?: SitemapOptions): string {\n  const urls: SitemapUrl[] = items.map(({ loc, ...news }) => ({ loc, news }));\n  return sitemap(urls, opts);\n}\n\n/** One entry of a video sitemap: a page URL plus a single video's metadata. */\nexport interface VideoSitemapItem extends SitemapVideo {\n  loc: string;\n}\n\n/**\n * Build a video extension sitemap (`<video:video>` per URL).\n * @example videoSitemap([{ loc: \"https://x.com/watch\", thumbnailLoc: \"https://x.com/t.jpg\", title: \"Clip\", description: \"…\", contentLoc: \"https://x.com/v.mp4\", duration: 120 }])\n */\nexport function videoSitemap(items: VideoSitemapItem[], opts?: SitemapOptions): string {\n  const urls: SitemapUrl[] = items.map(({ loc, ...video }) => ({ loc, videos: [video] }));\n  return sitemap(urls, opts);\n}\n\n/** One entry of an image sitemap: a page URL plus the images it contains. */\nexport interface ImageSitemapItem {\n  loc: string;\n  images: SitemapImage[];\n}\n\n/**\n * Build an image extension sitemap (`<image:image>` entries grouped per URL).\n * @example imageSitemap([{ loc: \"https://x.com/gallery\", images: [{ loc: \"https://x.com/1.jpg\", title: \"One\" }] }])\n */\nexport function imageSitemap(items: ImageSitemapItem[], opts?: SitemapOptions): string {\n  const urls: SitemapUrl[] = items.map((i) => ({ loc: i.loc, images: i.images }));\n  return sitemap(urls, opts);\n}\n\n/**\n * An XSL stylesheet that renders a raw `sitemap.xml` as a readable HTML table\n * in the browser. Serve it alongside your sitemap and reference it via the\n * `stylesheet` option on {@link sitemap} (or the extension sitemaps).\n * @example\n * // app/sitemap.xsl route → return sitemapStylesheet()\n * sitemap(urls, { stylesheet: \"/sitemap.xsl\" })\n */\nexport function sitemapStylesheet(): string {\n  return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<xsl:stylesheet version=\"1.0\"\n  xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"\n  xmlns:s=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n  <xsl:output method=\"html\" encoding=\"UTF-8\" indent=\"yes\"/>\n  <xsl:template match=\"/\">\n    <html lang=\"en\">\n      <head>\n        <meta charset=\"UTF-8\"/>\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"/>\n        <title>XML Sitemap</title>\n        <style>\n          :root { color-scheme: light dark; }\n          body { font: 14px/1.5 system-ui, sans-serif; margin: 0; padding: 2rem; background: #f7f8fa; color: #111; }\n          @media (prefers-color-scheme: dark) { body { background: #0e0f12; color: #e7e9ee; } tr:nth-child(even) td { background: #16181d; } th { background: #1b1e24; } a { color: #7aa2ff; } }\n          h1 { font-size: 1.4rem; margin: 0 0 .25rem; }\n          p.meta { color: #667; margin: 0 0 1.5rem; }\n          table { width: 100%; border-collapse: collapse; background: transparent; }\n          th, td { text-align: left; padding: .55rem .75rem; border-bottom: 1px solid #e2e4ea; vertical-align: top; }\n          @media (prefers-color-scheme: dark) { th, td { border-color: #24272e; } }\n          th { font-weight: 600; background: #eef0f4; }\n          tr:nth-child(even) td { background: #fbfbfd; }\n          td.url { word-break: break-all; }\n          a { color: #2456c9; text-decoration: none; }\n          a:hover { text-decoration: underline; }\n        </style>\n      </head>\n      <body>\n        <h1>XML Sitemap</h1>\n        <p class=\"meta\">This sitemap contains <xsl:value-of select=\"count(s:urlset/s:url)\"/> URL(s).</p>\n        <table>\n          <tr><th>URL</th><th>Last modified</th><th>Change freq.</th><th>Priority</th></tr>\n          <xsl:for-each select=\"s:urlset/s:url\">\n            <tr>\n              <td class=\"url\"><a href=\"{s:loc}\"><xsl:value-of select=\"s:loc\"/></a></td>\n              <td><xsl:value-of select=\"s:lastmod\"/></td>\n              <td><xsl:value-of select=\"s:changefreq\"/></td>\n              <td><xsl:value-of select=\"s:priority\"/></td>\n            </tr>\n          </xsl:for-each>\n        </table>\n      </body>\n    </html>\n  </xsl:template>\n</xsl:stylesheet>`;\n}\n"]}