@power-seo/sitemap 1.0.5 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -364,15 +364,9 @@ function toNextSitemap(urls) {
364
364
  const { valid } = validateSitemapUrl(url);
365
365
  if (!valid) continue;
366
366
  const entry = { url: url.loc };
367
- if (url.lastmod) {
368
- entry.lastModified = url.lastmod;
369
- }
370
- if (url.changefreq) {
371
- entry.changeFrequency = url.changefreq;
372
- }
373
- if (url.priority !== void 0) {
374
- entry.priority = url.priority;
375
- }
367
+ if (url.lastmod) entry.lastModified = url.lastmod;
368
+ if (url.changefreq) entry.changeFrequency = url.changefreq;
369
+ if (url.priority !== void 0) entry.priority = url.priority;
376
370
  entries.push(entry);
377
371
  }
378
372
  return entries;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/generator.ts","../src/types.ts","../src/sitemap-index.ts","../src/stream.ts","../src/validate.ts","../src/next-adapter.ts"],"sourcesContent":["// ============================================================================\n// @power-seo/sitemap — Public API\n// ============================================================================\n\nexport { generateSitemap } from './generator.js';\nexport { generateSitemapIndex, splitSitemap } from './sitemap-index.js';\nexport { streamSitemap } from './stream.js';\nexport { validateSitemapUrl } from './validate.js';\nexport { toNextSitemap } from './next-adapter.js';\nexport type { NextSitemapEntry } from './next-adapter.js';\n\nexport type {\n SitemapURL,\n SitemapImage,\n SitemapVideo,\n SitemapNews,\n SitemapConfig,\n SitemapIndexEntry,\n SitemapIndexConfig,\n SitemapValidationResult,\n} from './types.js';\n\nexport { MAX_URLS_PER_SITEMAP, MAX_SITEMAP_SIZE_BYTES } from './types.js';\n","// ============================================================================\n// @power-seo/sitemap — XML Sitemap Generator\n// ============================================================================\n\nimport type {\n SitemapConfig,\n SitemapURL,\n SitemapImage,\n SitemapVideo,\n SitemapNews,\n} from '@power-seo/core';\nimport { normalizeUrl } from '@power-seo/core';\n\n/** Escape special XML characters. */\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;');\n}\n\n/** Determine which namespace extensions are needed. */\nfunction detectNamespaces(urls: SitemapURL[]): { image: boolean; video: boolean; news: boolean } {\n let image = false;\n let video = false;\n let news = false;\n for (const url of urls) {\n if (url.images && url.images.length > 0) image = true;\n if (url.videos && url.videos.length > 0) video = true;\n if (url.news) news = true;\n if (image && video && news) break;\n }\n return { image, video, news };\n}\n\nfunction buildImageXml(images: SitemapImage[]): string {\n return images\n .map((img) => {\n let xml = ' <image:image>\\n';\n xml += ` <image:loc>${escapeXml(img.loc)}</image:loc>\\n`;\n if (img.caption) xml += ` <image:caption>${escapeXml(img.caption)}</image:caption>\\n`;\n if (img.geoLocation)\n xml += ` <image:geo_location>${escapeXml(img.geoLocation)}</image:geo_location>\\n`;\n if (img.title) xml += ` <image:title>${escapeXml(img.title)}</image:title>\\n`;\n if (img.license) xml += ` <image:license>${escapeXml(img.license)}</image:license>\\n`;\n xml += ' </image:image>\\n';\n return xml;\n })\n .join('');\n}\n\nfunction buildVideoXml(videos: SitemapVideo[]): string {\n return videos\n .map((vid) => {\n let xml = ' <video:video>\\n';\n xml += ` <video:thumbnail_loc>${escapeXml(vid.thumbnailLoc)}</video:thumbnail_loc>\\n`;\n xml += ` <video:title>${escapeXml(vid.title)}</video:title>\\n`;\n xml += ` <video:description>${escapeXml(vid.description)}</video:description>\\n`;\n if (vid.contentLoc)\n xml += ` <video:content_loc>${escapeXml(vid.contentLoc)}</video:content_loc>\\n`;\n if (vid.playerLoc)\n xml += ` <video:player_loc>${escapeXml(vid.playerLoc)}</video:player_loc>\\n`;\n if (vid.duration !== undefined)\n xml += ` <video:duration>${vid.duration}</video:duration>\\n`;\n if (vid.expirationDate)\n xml += ` <video:expiration_date>${escapeXml(vid.expirationDate)}</video:expiration_date>\\n`;\n if (vid.rating !== undefined) xml += ` <video:rating>${vid.rating}</video:rating>\\n`;\n if (vid.viewCount !== undefined)\n xml += ` <video:view_count>${vid.viewCount}</video:view_count>\\n`;\n if (vid.publicationDate)\n xml += ` <video:publication_date>${escapeXml(vid.publicationDate)}</video:publication_date>\\n`;\n if (vid.familyFriendly !== undefined)\n xml += ` <video:family_friendly>${vid.familyFriendly ? 'yes' : 'no'}</video:family_friendly>\\n`;\n if (vid.live !== undefined)\n xml += ` <video:live>${vid.live ? 'yes' : 'no'}</video:live>\\n`;\n xml += ' </video:video>\\n';\n return xml;\n })\n .join('');\n}\n\nfunction buildNewsXml(news: SitemapNews): string {\n let xml = ' <news:news>\\n';\n xml += ' <news:publication>\\n';\n xml += ` <news:name>${escapeXml(news.publication.name)}</news:name>\\n`;\n xml += ` <news:language>${escapeXml(news.publication.language)}</news:language>\\n`;\n xml += ' </news:publication>\\n';\n xml += ` <news:publication_date>${escapeXml(news.publicationDate)}</news:publication_date>\\n`;\n xml += ` <news:title>${escapeXml(news.title)}</news:title>\\n`;\n xml += ' </news:news>\\n';\n return xml;\n}\n\nfunction buildUrlXml(url: SitemapURL, hostname: string): string {\n const loc = url.loc.startsWith('http')\n ? url.loc\n : normalizeUrl(`${hostname}${url.loc.startsWith('/') ? '' : '/'}${url.loc}`);\n\n let xml = ' <url>\\n';\n xml += ` <loc>${escapeXml(loc)}</loc>\\n`;\n if (url.lastmod) xml += ` <lastmod>${escapeXml(url.lastmod)}</lastmod>\\n`;\n if (url.changefreq) xml += ` <changefreq>${url.changefreq}</changefreq>\\n`;\n if (url.priority !== undefined) xml += ` <priority>${url.priority.toFixed(1)}</priority>\\n`;\n if (url.images && url.images.length > 0) xml += buildImageXml(url.images);\n if (url.videos && url.videos.length > 0) xml += buildVideoXml(url.videos);\n if (url.news) xml += buildNewsXml(url.news);\n xml += ' </url>\\n';\n return xml;\n}\n\n/**\n * Generate an XML sitemap string from a sitemap configuration.\n *\n * @example\n * ```ts\n * const xml = generateSitemap({\n * hostname: 'https://example.com',\n * urls: [\n * { loc: '/', changefreq: 'daily', priority: 1.0 },\n * { loc: '/about', changefreq: 'monthly', priority: 0.8 },\n * ],\n * });\n * ```\n */\nexport function generateSitemap(config: SitemapConfig): string {\n const { hostname, urls } = config;\n const ns = detectNamespaces(urls);\n\n let xml = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n';\n xml += '<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\"';\n if (ns.image) xml += '\\n xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\"';\n if (ns.video) xml += '\\n xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\"';\n if (ns.news) xml += '\\n xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\"';\n xml += '>\\n';\n\n for (const url of urls) {\n xml += buildUrlXml(url, hostname);\n }\n\n xml += '</urlset>\\n';\n return xml;\n}\n","// ============================================================================\n// @power-seo/sitemap — Types\n// ============================================================================\n\nexport type {\n SitemapURL,\n SitemapImage,\n SitemapVideo,\n SitemapNews,\n SitemapConfig,\n} from '@power-seo/core';\n\n/** A sitemap entry in a sitemap index. */\nexport interface SitemapIndexEntry {\n loc: string;\n lastmod?: string;\n}\n\n/** Configuration for the sitemap index generator. */\nexport interface SitemapIndexConfig {\n sitemaps: SitemapIndexEntry[];\n}\n\n/** Result of URL validation. */\nexport interface SitemapValidationResult {\n valid: boolean;\n errors: string[];\n warnings: string[];\n}\n\n/** Maximum URLs allowed per sitemap file. */\nexport const MAX_URLS_PER_SITEMAP = 50_000 as const;\n\n/** Maximum sitemap file size in bytes (50MB). */\nexport const MAX_SITEMAP_SIZE_BYTES = 52_428_800 as const;\n","// ============================================================================\n// @power-seo/sitemap — Sitemap Index Generator\n// ============================================================================\n\nimport type { SitemapConfig } from '@power-seo/core';\nimport type { SitemapIndexConfig, SitemapIndexEntry } from './types.js';\nimport { MAX_URLS_PER_SITEMAP } from './types.js';\nimport { generateSitemap } from './generator.js';\n\n/** Escape special XML characters. */\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;');\n}\n\n/**\n * Generate a sitemap index XML string.\n *\n * @example\n * ```ts\n * const indexXml = generateSitemapIndex({\n * sitemaps: [\n * { loc: 'https://example.com/sitemap-0.xml', lastmod: '2024-01-01' },\n * { loc: 'https://example.com/sitemap-1.xml', lastmod: '2024-01-01' },\n * ],\n * });\n * ```\n */\nexport function generateSitemapIndex(config: SitemapIndexConfig): string {\n let xml = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n';\n xml += '<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\\n';\n\n for (const sitemap of config.sitemaps) {\n xml += ' <sitemap>\\n';\n xml += ` <loc>${escapeXml(sitemap.loc)}</loc>\\n`;\n if (sitemap.lastmod) {\n xml += ` <lastmod>${escapeXml(sitemap.lastmod)}</lastmod>\\n`;\n }\n xml += ' </sitemap>\\n';\n }\n\n xml += '</sitemapindex>\\n';\n return xml;\n}\n\n/**\n * Split a large sitemap config into multiple sitemaps + an index.\n *\n * When a site has more than 50,000 URLs, this function splits them into\n * multiple sitemap files and returns both the individual sitemaps and\n * the index that references them.\n *\n * @example\n * ```ts\n * const { index, sitemaps } = splitSitemap({\n * hostname: 'https://example.com',\n * urls: largeUrlArray,\n * });\n * // sitemaps[0].xml, sitemaps[1].xml, etc.\n * // index = sitemap index XML\n * ```\n */\nexport function splitSitemap(\n config: SitemapConfig,\n sitemapUrlPattern = '/sitemap-{index}.xml',\n): { index: string; sitemaps: Array<{ filename: string; xml: string }> } {\n const maxPerSitemap = config.maxUrlsPerSitemap ?? MAX_URLS_PER_SITEMAP;\n const { hostname, urls } = config;\n\n if (urls.length <= maxPerSitemap) {\n const xml = generateSitemap(config);\n const filename = sitemapUrlPattern.replace('{index}', '0');\n const indexEntries: SitemapIndexEntry[] = [{ loc: `${hostname}${filename}` }];\n return {\n index: generateSitemapIndex({ sitemaps: indexEntries }),\n sitemaps: [{ filename, xml }],\n };\n }\n\n const sitemaps: Array<{ filename: string; xml: string }> = [];\n const indexEntries: SitemapIndexEntry[] = [];\n\n for (let i = 0; i < urls.length; i += maxPerSitemap) {\n const chunk = urls.slice(i, i + maxPerSitemap);\n const chunkIndex = Math.floor(i / maxPerSitemap);\n const filename = sitemapUrlPattern.replace('{index}', String(chunkIndex));\n const xml = generateSitemap({ hostname, urls: chunk });\n\n sitemaps.push({ filename, xml });\n indexEntries.push({ loc: `${hostname}${filename}` });\n }\n\n return {\n index: generateSitemapIndex({ sitemaps: indexEntries }),\n sitemaps,\n };\n}\n","// ============================================================================\n// @power-seo/sitemap — Streaming Sitemap Generator\n// ============================================================================\n\nimport type { SitemapURL } from '@power-seo/core';\nimport { normalizeUrl } from '@power-seo/core';\n\n/** Escape special XML characters. */\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;');\n}\n\n/**\n * A streaming sitemap generator that yields XML chunks.\n *\n * Useful for server responses where you want to stream the sitemap\n * instead of building the entire XML string in memory.\n *\n * @example\n * ```ts\n * const stream = streamSitemap('https://example.com', urls);\n * for (const chunk of stream) {\n * response.write(chunk);\n * }\n * ```\n */\nexport function* streamSitemap(\n hostname: string,\n urls: Iterable<SitemapURL>,\n): Generator<string, void, undefined> {\n yield '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n';\n yield '<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\"\\n';\n yield ' xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\"\\n';\n yield ' xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\"\\n';\n yield ' xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\">\\n';\n\n for (const url of urls) {\n const loc = url.loc.startsWith('http')\n ? url.loc\n : normalizeUrl(`${hostname}${url.loc.startsWith('/') ? '' : '/'}${url.loc}`);\n\n yield ' <url>\\n';\n yield ` <loc>${escapeXml(loc)}</loc>\\n`;\n\n if (url.lastmod) yield ` <lastmod>${escapeXml(url.lastmod)}</lastmod>\\n`;\n if (url.changefreq) yield ` <changefreq>${url.changefreq}</changefreq>\\n`;\n if (url.priority !== undefined) yield ` <priority>${url.priority.toFixed(1)}</priority>\\n`;\n\n // Images\n if (url.images) {\n for (const img of url.images) {\n yield ' <image:image>\\n';\n yield ` <image:loc>${escapeXml(img.loc)}</image:loc>\\n`;\n if (img.caption) yield ` <image:caption>${escapeXml(img.caption)}</image:caption>\\n`;\n if (img.title) yield ` <image:title>${escapeXml(img.title)}</image:title>\\n`;\n yield ' </image:image>\\n';\n }\n }\n\n // Videos\n if (url.videos) {\n for (const vid of url.videos) {\n yield ' <video:video>\\n';\n yield ` <video:thumbnail_loc>${escapeXml(vid.thumbnailLoc)}</video:thumbnail_loc>\\n`;\n yield ` <video:title>${escapeXml(vid.title)}</video:title>\\n`;\n yield ` <video:description>${escapeXml(vid.description)}</video:description>\\n`;\n if (vid.contentLoc)\n yield ` <video:content_loc>${escapeXml(vid.contentLoc)}</video:content_loc>\\n`;\n if (vid.playerLoc)\n yield ` <video:player_loc>${escapeXml(vid.playerLoc)}</video:player_loc>\\n`;\n yield ' </video:video>\\n';\n }\n }\n\n // News\n if (url.news) {\n yield ' <news:news>\\n';\n yield ' <news:publication>\\n';\n yield ` <news:name>${escapeXml(url.news.publication.name)}</news:name>\\n`;\n yield ` <news:language>${escapeXml(url.news.publication.language)}</news:language>\\n`;\n yield ' </news:publication>\\n';\n yield ` <news:publication_date>${escapeXml(url.news.publicationDate)}</news:publication_date>\\n`;\n yield ` <news:title>${escapeXml(url.news.title)}</news:title>\\n`;\n yield ' </news:news>\\n';\n }\n\n yield ' </url>\\n';\n }\n\n yield '</urlset>\\n';\n}\n","// ============================================================================\n// @power-seo/sitemap — URL Validation\n// ============================================================================\n\nimport type { SitemapURL } from '@power-seo/core';\nimport { isAbsoluteUrl, MAX_URL_LENGTH } from '@power-seo/core';\nimport type { SitemapValidationResult } from './types.js';\n\nconst VALID_CHANGEFREQ = ['always', 'hourly', 'daily', 'weekly', 'monthly', 'yearly', 'never'];\n\n/**\n * Validate a sitemap URL entry against the sitemap protocol spec.\n */\nexport function validateSitemapUrl(url: SitemapURL): SitemapValidationResult {\n const errors: string[] = [];\n const warnings: string[] = [];\n\n // loc is required\n if (!url.loc || url.loc.trim().length === 0) {\n errors.push('URL \"loc\" is required and cannot be empty.');\n } else {\n if (!isAbsoluteUrl(url.loc)) {\n errors.push(`URL \"${url.loc}\" must be an absolute URL (starting with http:// or https://).`);\n }\n\n if (url.loc.length > 2048) {\n errors.push(`URL \"${url.loc}\" exceeds the maximum length of 2048 characters.`);\n }\n\n if (url.loc.length > MAX_URL_LENGTH) {\n warnings.push(\n `URL \"${url.loc}\" is ${url.loc.length} characters. URLs under ${MAX_URL_LENGTH} characters are recommended for SEO.`,\n );\n }\n }\n\n // lastmod validation\n if (url.lastmod) {\n const dateRegex = /^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}(:\\d{2})?([+-]\\d{2}:\\d{2}|Z)?)?$/;\n if (!dateRegex.test(url.lastmod)) {\n errors.push(\n `\"lastmod\" value \"${url.lastmod}\" is not a valid W3C datetime format (YYYY-MM-DD or ISO 8601).`,\n );\n }\n }\n\n // changefreq validation\n if (url.changefreq && !VALID_CHANGEFREQ.includes(url.changefreq)) {\n errors.push(\n `\"changefreq\" value \"${url.changefreq}\" is invalid. Must be one of: ${VALID_CHANGEFREQ.join(', ')}.`,\n );\n }\n\n // priority validation\n if (url.priority !== undefined) {\n if (url.priority < 0 || url.priority > 1) {\n errors.push(`\"priority\" value ${url.priority} is out of range. Must be between 0.0 and 1.0.`);\n }\n }\n\n // Image validation\n if (url.images) {\n for (let i = 0; i < url.images.length; i++) {\n const img = url.images[i]!;\n if (!img.loc || img.loc.trim().length === 0) {\n errors.push(`Image ${i + 1}: \"loc\" is required.`);\n } else if (!isAbsoluteUrl(img.loc)) {\n errors.push(`Image ${i + 1}: \"${img.loc}\" must be an absolute URL.`);\n }\n }\n if (url.images.length > 1000) {\n warnings.push(\n `URL has ${url.images.length} images. Google supports up to 1,000 images per page.`,\n );\n }\n }\n\n // Video validation\n if (url.videos) {\n for (let i = 0; i < url.videos.length; i++) {\n const vid = url.videos[i]!;\n if (!vid.title) errors.push(`Video ${i + 1}: \"title\" is required.`);\n if (!vid.description) errors.push(`Video ${i + 1}: \"description\" is required.`);\n if (!vid.thumbnailLoc) errors.push(`Video ${i + 1}: \"thumbnailLoc\" is required.`);\n if (!vid.contentLoc && !vid.playerLoc) {\n errors.push(`Video ${i + 1}: either \"contentLoc\" or \"playerLoc\" must be provided.`);\n }\n if (vid.rating !== undefined && (vid.rating < 0 || vid.rating > 5)) {\n errors.push(`Video ${i + 1}: \"rating\" must be between 0.0 and 5.0.`);\n }\n }\n }\n\n // News validation\n if (url.news) {\n if (!url.news.publication?.name) errors.push('News: \"publication.name\" is required.');\n if (!url.news.publication?.language) errors.push('News: \"publication.language\" is required.');\n if (!url.news.publicationDate) errors.push('News: \"publicationDate\" is required.');\n if (!url.news.title) errors.push('News: \"title\" is required.');\n }\n\n return {\n valid: errors.length === 0,\n errors,\n warnings,\n };\n}\n","// ============================================================================\n// @power-seo/sitemap — Next.js App Router Adapter\n// ============================================================================\n\nimport type { SitemapURL } from '@power-seo/core';\nimport { validateSitemapUrl } from './validate.js';\n\n/**\n * A plain object that matches the shape of Next.js `MetadataRoute.Sitemap[number]`.\n * Typed locally so `@power-seo/sitemap` has no dependency on `next`.\n */\nexport interface NextSitemapEntry {\n url: string;\n lastModified?: string | Date;\n changeFrequency?:\n | 'always'\n | 'hourly'\n | 'daily'\n | 'weekly'\n | 'monthly'\n | 'yearly'\n | 'never';\n priority?: number;\n alternates?: {\n languages?: Record<string, string>;\n };\n}\n\n/**\n * Convert an array of `SitemapURL` objects to a Next.js-compatible sitemap array.\n *\n * Use this in `app/sitemap.ts` to bridge `@power-seo/sitemap` with Next.js's\n * built-in `MetadataRoute.Sitemap` convention.\n *\n * @example\n * ```ts\n * // app/sitemap.ts\n * import type { MetadataRoute } from 'next';\n * import { toNextSitemap } from '@power-seo/sitemap';\n *\n * export default async function sitemap(): Promise<MetadataRoute.Sitemap> {\n * const urls = [\n * { loc: 'https://example.com', changefreq: 'daily', priority: 1.0 },\n * { loc: 'https://example.com/about', changefreq: 'monthly', priority: 0.8 },\n * ];\n * return toNextSitemap(urls) as MetadataRoute.Sitemap;\n * }\n * ```\n */\nexport function toNextSitemap(urls: SitemapURL[]): NextSitemapEntry[] {\n const entries: NextSitemapEntry[] = [];\n\n for (const url of urls) {\n const { valid } = validateSitemapUrl(url);\n if (!valid) continue;\n\n const entry: NextSitemapEntry = { url: url.loc };\n\n if (url.lastmod) {\n entry.lastModified = url.lastmod;\n }\n\n if (url.changefreq) {\n entry.changeFrequency = url.changefreq as NextSitemapEntry['changeFrequency'];\n }\n\n if (url.priority !== undefined) {\n entry.priority = url.priority;\n }\n\n entries.push(entry);\n }\n\n return entries;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACWA,kBAA6B;AAG7B,SAAS,UAAU,KAAqB;AACtC,SAAO,IACJ,QAAQ,MAAM,OAAO,EACrB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,QAAQ,EACtB,QAAQ,MAAM,QAAQ;AAC3B;AAGA,SAAS,iBAAiB,MAAuE;AAC/F,MAAI,QAAQ;AACZ,MAAI,QAAQ;AACZ,MAAI,OAAO;AACX,aAAW,OAAO,MAAM;AACtB,QAAI,IAAI,UAAU,IAAI,OAAO,SAAS,EAAG,SAAQ;AACjD,QAAI,IAAI,UAAU,IAAI,OAAO,SAAS,EAAG,SAAQ;AACjD,QAAI,IAAI,KAAM,QAAO;AACrB,QAAI,SAAS,SAAS,KAAM;AAAA,EAC9B;AACA,SAAO,EAAE,OAAO,OAAO,KAAK;AAC9B;AAEA,SAAS,cAAc,QAAgC;AACrD,SAAO,OACJ,IAAI,CAAC,QAAQ;AACZ,QAAI,MAAM;AACV,WAAO,oBAAoB,UAAU,IAAI,GAAG,CAAC;AAAA;AAC7C,QAAI,IAAI,QAAS,QAAO,wBAAwB,UAAU,IAAI,OAAO,CAAC;AAAA;AACtE,QAAI,IAAI;AACN,aAAO,6BAA6B,UAAU,IAAI,WAAW,CAAC;AAAA;AAChE,QAAI,IAAI,MAAO,QAAO,sBAAsB,UAAU,IAAI,KAAK,CAAC;AAAA;AAChE,QAAI,IAAI,QAAS,QAAO,wBAAwB,UAAU,IAAI,OAAO,CAAC;AAAA;AACtE,WAAO;AACP,WAAO;AAAA,EACT,CAAC,EACA,KAAK,EAAE;AACZ;AAEA,SAAS,cAAc,QAAgC;AACrD,SAAO,OACJ,IAAI,CAAC,QAAQ;AACZ,QAAI,MAAM;AACV,WAAO,8BAA8B,UAAU,IAAI,YAAY,CAAC;AAAA;AAChE,WAAO,sBAAsB,UAAU,IAAI,KAAK,CAAC;AAAA;AACjD,WAAO,4BAA4B,UAAU,IAAI,WAAW,CAAC;AAAA;AAC7D,QAAI,IAAI;AACN,aAAO,4BAA4B,UAAU,IAAI,UAAU,CAAC;AAAA;AAC9D,QAAI,IAAI;AACN,aAAO,2BAA2B,UAAU,IAAI,SAAS,CAAC;AAAA;AAC5D,QAAI,IAAI,aAAa;AACnB,aAAO,yBAAyB,IAAI,QAAQ;AAAA;AAC9C,QAAI,IAAI;AACN,aAAO,gCAAgC,UAAU,IAAI,cAAc,CAAC;AAAA;AACtE,QAAI,IAAI,WAAW,OAAW,QAAO,uBAAuB,IAAI,MAAM;AAAA;AACtE,QAAI,IAAI,cAAc;AACpB,aAAO,2BAA2B,IAAI,SAAS;AAAA;AACjD,QAAI,IAAI;AACN,aAAO,iCAAiC,UAAU,IAAI,eAAe,CAAC;AAAA;AACxE,QAAI,IAAI,mBAAmB;AACzB,aAAO,gCAAgC,IAAI,iBAAiB,QAAQ,IAAI;AAAA;AAC1E,QAAI,IAAI,SAAS;AACf,aAAO,qBAAqB,IAAI,OAAO,QAAQ,IAAI;AAAA;AACrD,WAAO;AACP,WAAO;AAAA,EACT,CAAC,EACA,KAAK,EAAE;AACZ;AAEA,SAAS,aAAa,MAA2B;AAC/C,MAAI,MAAM;AACV,SAAO;AACP,SAAO,sBAAsB,UAAU,KAAK,YAAY,IAAI,CAAC;AAAA;AAC7D,SAAO,0BAA0B,UAAU,KAAK,YAAY,QAAQ,CAAC;AAAA;AACrE,SAAO;AACP,SAAO,gCAAgC,UAAU,KAAK,eAAe,CAAC;AAAA;AACtE,SAAO,qBAAqB,UAAU,KAAK,KAAK,CAAC;AAAA;AACjD,SAAO;AACP,SAAO;AACT;AAEA,SAAS,YAAY,KAAiB,UAA0B;AAC9D,QAAM,MAAM,IAAI,IAAI,WAAW,MAAM,IACjC,IAAI,UACJ,0BAAa,GAAG,QAAQ,GAAG,IAAI,IAAI,WAAW,GAAG,IAAI,KAAK,GAAG,GAAG,IAAI,GAAG,EAAE;AAE7E,MAAI,MAAM;AACV,SAAO,YAAY,UAAU,GAAG,CAAC;AAAA;AACjC,MAAI,IAAI,QAAS,QAAO,gBAAgB,UAAU,IAAI,OAAO,CAAC;AAAA;AAC9D,MAAI,IAAI,WAAY,QAAO,mBAAmB,IAAI,UAAU;AAAA;AAC5D,MAAI,IAAI,aAAa,OAAW,QAAO,iBAAiB,IAAI,SAAS,QAAQ,CAAC,CAAC;AAAA;AAC/E,MAAI,IAAI,UAAU,IAAI,OAAO,SAAS,EAAG,QAAO,cAAc,IAAI,MAAM;AACxE,MAAI,IAAI,UAAU,IAAI,OAAO,SAAS,EAAG,QAAO,cAAc,IAAI,MAAM;AACxE,MAAI,IAAI,KAAM,QAAO,aAAa,IAAI,IAAI;AAC1C,SAAO;AACP,SAAO;AACT;AAgBO,SAAS,gBAAgB,QAA+B;AAC7D,QAAM,EAAE,UAAU,KAAK,IAAI;AAC3B,QAAM,KAAK,iBAAiB,IAAI;AAEhC,MAAI,MAAM;AACV,SAAO;AACP,MAAI,GAAG,MAAO,QAAO;AACrB,MAAI,GAAG,MAAO,QAAO;AACrB,MAAI,GAAG,KAAM,QAAO;AACpB,SAAO;AAEP,aAAW,OAAO,MAAM;AACtB,WAAO,YAAY,KAAK,QAAQ;AAAA,EAClC;AAEA,SAAO;AACP,SAAO;AACT;;;AChHO,IAAM,uBAAuB;AAG7B,IAAM,yBAAyB;;;ACxBtC,SAASA,WAAU,KAAqB;AACtC,SAAO,IACJ,QAAQ,MAAM,OAAO,EACrB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,QAAQ,EACtB,QAAQ,MAAM,QAAQ;AAC3B;AAeO,SAAS,qBAAqB,QAAoC;AACvE,MAAI,MAAM;AACV,SAAO;AAEP,aAAW,WAAW,OAAO,UAAU;AACrC,WAAO;AACP,WAAO,YAAYA,WAAU,QAAQ,GAAG,CAAC;AAAA;AACzC,QAAI,QAAQ,SAAS;AACnB,aAAO,gBAAgBA,WAAU,QAAQ,OAAO,CAAC;AAAA;AAAA,IACnD;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AACP,SAAO;AACT;AAmBO,SAAS,aACd,QACA,oBAAoB,wBACmD;AACvE,QAAM,gBAAgB,OAAO,qBAAqB;AAClD,QAAM,EAAE,UAAU,KAAK,IAAI;AAE3B,MAAI,KAAK,UAAU,eAAe;AAChC,UAAM,MAAM,gBAAgB,MAAM;AAClC,UAAM,WAAW,kBAAkB,QAAQ,WAAW,GAAG;AACzD,UAAMC,gBAAoC,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,QAAQ,GAAG,CAAC;AAC5E,WAAO;AAAA,MACL,OAAO,qBAAqB,EAAE,UAAUA,cAAa,CAAC;AAAA,MACtD,UAAU,CAAC,EAAE,UAAU,IAAI,CAAC;AAAA,IAC9B;AAAA,EACF;AAEA,QAAM,WAAqD,CAAC;AAC5D,QAAM,eAAoC,CAAC;AAE3C,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,eAAe;AACnD,UAAM,QAAQ,KAAK,MAAM,GAAG,IAAI,aAAa;AAC7C,UAAM,aAAa,KAAK,MAAM,IAAI,aAAa;AAC/C,UAAM,WAAW,kBAAkB,QAAQ,WAAW,OAAO,UAAU,CAAC;AACxE,UAAM,MAAM,gBAAgB,EAAE,UAAU,MAAM,MAAM,CAAC;AAErD,aAAS,KAAK,EAAE,UAAU,IAAI,CAAC;AAC/B,iBAAa,KAAK,EAAE,KAAK,GAAG,QAAQ,GAAG,QAAQ,GAAG,CAAC;AAAA,EACrD;AAEA,SAAO;AAAA,IACL,OAAO,qBAAqB,EAAE,UAAU,aAAa,CAAC;AAAA,IACtD;AAAA,EACF;AACF;;;AC/FA,IAAAC,eAA6B;AAG7B,SAASC,WAAU,KAAqB;AACtC,SAAO,IACJ,QAAQ,MAAM,OAAO,EACrB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,QAAQ,EACtB,QAAQ,MAAM,QAAQ;AAC3B;AAgBO,UAAU,cACf,UACA,MACoC;AACpC,QAAM;AACN,QAAM;AACN,QAAM;AACN,QAAM;AACN,QAAM;AAEN,aAAW,OAAO,MAAM;AACtB,UAAM,MAAM,IAAI,IAAI,WAAW,MAAM,IACjC,IAAI,UACJ,2BAAa,GAAG,QAAQ,GAAG,IAAI,IAAI,WAAW,GAAG,IAAI,KAAK,GAAG,GAAG,IAAI,GAAG,EAAE;AAE7E,UAAM;AACN,UAAM,YAAYA,WAAU,GAAG,CAAC;AAAA;AAEhC,QAAI,IAAI,QAAS,OAAM,gBAAgBA,WAAU,IAAI,OAAO,CAAC;AAAA;AAC7D,QAAI,IAAI,WAAY,OAAM,mBAAmB,IAAI,UAAU;AAAA;AAC3D,QAAI,IAAI,aAAa,OAAW,OAAM,iBAAiB,IAAI,SAAS,QAAQ,CAAC,CAAC;AAAA;AAG9E,QAAI,IAAI,QAAQ;AACd,iBAAW,OAAO,IAAI,QAAQ;AAC5B,cAAM;AACN,cAAM,oBAAoBA,WAAU,IAAI,GAAG,CAAC;AAAA;AAC5C,YAAI,IAAI,QAAS,OAAM,wBAAwBA,WAAU,IAAI,OAAO,CAAC;AAAA;AACrE,YAAI,IAAI,MAAO,OAAM,sBAAsBA,WAAU,IAAI,KAAK,CAAC;AAAA;AAC/D,cAAM;AAAA,MACR;AAAA,IACF;AAGA,QAAI,IAAI,QAAQ;AACd,iBAAW,OAAO,IAAI,QAAQ;AAC5B,cAAM;AACN,cAAM,8BAA8BA,WAAU,IAAI,YAAY,CAAC;AAAA;AAC/D,cAAM,sBAAsBA,WAAU,IAAI,KAAK,CAAC;AAAA;AAChD,cAAM,4BAA4BA,WAAU,IAAI,WAAW,CAAC;AAAA;AAC5D,YAAI,IAAI;AACN,gBAAM,4BAA4BA,WAAU,IAAI,UAAU,CAAC;AAAA;AAC7D,YAAI,IAAI;AACN,gBAAM,2BAA2BA,WAAU,IAAI,SAAS,CAAC;AAAA;AAC3D,cAAM;AAAA,MACR;AAAA,IACF;AAGA,QAAI,IAAI,MAAM;AACZ,YAAM;AACN,YAAM;AACN,YAAM,sBAAsBA,WAAU,IAAI,KAAK,YAAY,IAAI,CAAC;AAAA;AAChE,YAAM,0BAA0BA,WAAU,IAAI,KAAK,YAAY,QAAQ,CAAC;AAAA;AACxE,YAAM;AACN,YAAM,gCAAgCA,WAAU,IAAI,KAAK,eAAe,CAAC;AAAA;AACzE,YAAM,qBAAqBA,WAAU,IAAI,KAAK,KAAK,CAAC;AAAA;AACpD,YAAM;AAAA,IACR;AAEA,UAAM;AAAA,EACR;AAEA,QAAM;AACR;;;AC1FA,IAAAC,eAA8C;AAG9C,IAAM,mBAAmB,CAAC,UAAU,UAAU,SAAS,UAAU,WAAW,UAAU,OAAO;AAKtF,SAAS,mBAAmB,KAA0C;AAC3E,QAAM,SAAmB,CAAC;AAC1B,QAAM,WAAqB,CAAC;AAG5B,MAAI,CAAC,IAAI,OAAO,IAAI,IAAI,KAAK,EAAE,WAAW,GAAG;AAC3C,WAAO,KAAK,4CAA4C;AAAA,EAC1D,OAAO;AACL,QAAI,KAAC,4BAAc,IAAI,GAAG,GAAG;AAC3B,aAAO,KAAK,QAAQ,IAAI,GAAG,gEAAgE;AAAA,IAC7F;AAEA,QAAI,IAAI,IAAI,SAAS,MAAM;AACzB,aAAO,KAAK,QAAQ,IAAI,GAAG,kDAAkD;AAAA,IAC/E;AAEA,QAAI,IAAI,IAAI,SAAS,6BAAgB;AACnC,eAAS;AAAA,QACP,QAAQ,IAAI,GAAG,QAAQ,IAAI,IAAI,MAAM,2BAA2B,2BAAc;AAAA,MAChF;AAAA,IACF;AAAA,EACF;AAGA,MAAI,IAAI,SAAS;AACf,UAAM,YAAY;AAClB,QAAI,CAAC,UAAU,KAAK,IAAI,OAAO,GAAG;AAChC,aAAO;AAAA,QACL,oBAAoB,IAAI,OAAO;AAAA,MACjC;AAAA,IACF;AAAA,EACF;AAGA,MAAI,IAAI,cAAc,CAAC,iBAAiB,SAAS,IAAI,UAAU,GAAG;AAChE,WAAO;AAAA,MACL,uBAAuB,IAAI,UAAU,iCAAiC,iBAAiB,KAAK,IAAI,CAAC;AAAA,IACnG;AAAA,EACF;AAGA,MAAI,IAAI,aAAa,QAAW;AAC9B,QAAI,IAAI,WAAW,KAAK,IAAI,WAAW,GAAG;AACxC,aAAO,KAAK,oBAAoB,IAAI,QAAQ,gDAAgD;AAAA,IAC9F;AAAA,EACF;AAGA,MAAI,IAAI,QAAQ;AACd,aAAS,IAAI,GAAG,IAAI,IAAI,OAAO,QAAQ,KAAK;AAC1C,YAAM,MAAM,IAAI,OAAO,CAAC;AACxB,UAAI,CAAC,IAAI,OAAO,IAAI,IAAI,KAAK,EAAE,WAAW,GAAG;AAC3C,eAAO,KAAK,SAAS,IAAI,CAAC,sBAAsB;AAAA,MAClD,WAAW,KAAC,4BAAc,IAAI,GAAG,GAAG;AAClC,eAAO,KAAK,SAAS,IAAI,CAAC,MAAM,IAAI,GAAG,4BAA4B;AAAA,MACrE;AAAA,IACF;AACA,QAAI,IAAI,OAAO,SAAS,KAAM;AAC5B,eAAS;AAAA,QACP,WAAW,IAAI,OAAO,MAAM;AAAA,MAC9B;AAAA,IACF;AAAA,EACF;AAGA,MAAI,IAAI,QAAQ;AACd,aAAS,IAAI,GAAG,IAAI,IAAI,OAAO,QAAQ,KAAK;AAC1C,YAAM,MAAM,IAAI,OAAO,CAAC;AACxB,UAAI,CAAC,IAAI,MAAO,QAAO,KAAK,SAAS,IAAI,CAAC,wBAAwB;AAClE,UAAI,CAAC,IAAI,YAAa,QAAO,KAAK,SAAS,IAAI,CAAC,8BAA8B;AAC9E,UAAI,CAAC,IAAI,aAAc,QAAO,KAAK,SAAS,IAAI,CAAC,+BAA+B;AAChF,UAAI,CAAC,IAAI,cAAc,CAAC,IAAI,WAAW;AACrC,eAAO,KAAK,SAAS,IAAI,CAAC,wDAAwD;AAAA,MACpF;AACA,UAAI,IAAI,WAAW,WAAc,IAAI,SAAS,KAAK,IAAI,SAAS,IAAI;AAClE,eAAO,KAAK,SAAS,IAAI,CAAC,yCAAyC;AAAA,MACrE;AAAA,IACF;AAAA,EACF;AAGA,MAAI,IAAI,MAAM;AACZ,QAAI,CAAC,IAAI,KAAK,aAAa,KAAM,QAAO,KAAK,uCAAuC;AACpF,QAAI,CAAC,IAAI,KAAK,aAAa,SAAU,QAAO,KAAK,2CAA2C;AAC5F,QAAI,CAAC,IAAI,KAAK,gBAAiB,QAAO,KAAK,sCAAsC;AACjF,QAAI,CAAC,IAAI,KAAK,MAAO,QAAO,KAAK,4BAA4B;AAAA,EAC/D;AAEA,SAAO;AAAA,IACL,OAAO,OAAO,WAAW;AAAA,IACzB;AAAA,IACA;AAAA,EACF;AACF;;;ACzDO,SAAS,cAAc,MAAwC;AACpE,QAAM,UAA8B,CAAC;AAErC,aAAW,OAAO,MAAM;AACtB,UAAM,EAAE,MAAM,IAAI,mBAAmB,GAAG;AACxC,QAAI,CAAC,MAAO;AAEZ,UAAM,QAA0B,EAAE,KAAK,IAAI,IAAI;AAE/C,QAAI,IAAI,SAAS;AACf,YAAM,eAAe,IAAI;AAAA,IAC3B;AAEA,QAAI,IAAI,YAAY;AAClB,YAAM,kBAAkB,IAAI;AAAA,IAC9B;AAEA,QAAI,IAAI,aAAa,QAAW;AAC9B,YAAM,WAAW,IAAI;AAAA,IACvB;AAEA,YAAQ,KAAK,KAAK;AAAA,EACpB;AAEA,SAAO;AACT;","names":["escapeXml","indexEntries","import_core","escapeXml","import_core"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/generator.ts","../src/types.ts","../src/sitemap-index.ts","../src/stream.ts","../src/validate.ts","../src/next-adapter.ts"],"sourcesContent":["// @power-seo/sitemap — Public API\n// ----------------------------------------------------------------------------\n\nexport { generateSitemap } from './generator.js';\nexport { generateSitemapIndex, splitSitemap } from './sitemap-index.js';\nexport { streamSitemap } from './stream.js';\nexport { validateSitemapUrl } from './validate.js';\nexport { toNextSitemap } from './next-adapter.js';\nexport type { NextSitemapEntry } from './next-adapter.js';\n\nexport type {\n SitemapURL,\n SitemapImage,\n SitemapVideo,\n SitemapNews,\n SitemapConfig,\n SitemapIndexEntry,\n SitemapIndexConfig,\n SitemapValidationResult,\n} from './types.js';\n\nexport { MAX_URLS_PER_SITEMAP, MAX_SITEMAP_SIZE_BYTES } from './types.js';\n","// @power-seo/sitemap — XML Sitemap Generator\n// ----------------------------------------------------------------------------\n\nimport type {\n SitemapConfig,\n SitemapURL,\n SitemapImage,\n SitemapVideo,\n SitemapNews,\n} from '@power-seo/core';\nimport { normalizeUrl } from '@power-seo/core';\n\n/** Escape special XML characters. */\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;');\n}\n\n/** Determine which namespace extensions are needed. */\nfunction detectNamespaces(urls: SitemapURL[]): { image: boolean; video: boolean; news: boolean } {\n let image = false;\n let video = false;\n let news = false;\n for (const url of urls) {\n if (url.images && url.images.length > 0) image = true;\n if (url.videos && url.videos.length > 0) video = true;\n if (url.news) news = true;\n if (image && video && news) break;\n }\n return { image, video, news };\n}\n\nfunction buildImageXml(images: SitemapImage[]): string {\n return images\n .map((img) => {\n let xml = ' <image:image>\\n';\n xml += ` <image:loc>${escapeXml(img.loc)}</image:loc>\\n`;\n if (img.caption) xml += ` <image:caption>${escapeXml(img.caption)}</image:caption>\\n`;\n if (img.geoLocation)\n xml += ` <image:geo_location>${escapeXml(img.geoLocation)}</image:geo_location>\\n`;\n if (img.title) xml += ` <image:title>${escapeXml(img.title)}</image:title>\\n`;\n if (img.license) xml += ` <image:license>${escapeXml(img.license)}</image:license>\\n`;\n xml += ' </image:image>\\n';\n return xml;\n })\n .join('');\n}\n\nfunction buildVideoXml(videos: SitemapVideo[]): string {\n return videos\n .map((vid) => {\n let xml = ' <video:video>\\n';\n xml += ` <video:thumbnail_loc>${escapeXml(vid.thumbnailLoc)}</video:thumbnail_loc>\\n`;\n xml += ` <video:title>${escapeXml(vid.title)}</video:title>\\n`;\n xml += ` <video:description>${escapeXml(vid.description)}</video:description>\\n`;\n if (vid.contentLoc)\n xml += ` <video:content_loc>${escapeXml(vid.contentLoc)}</video:content_loc>\\n`;\n if (vid.playerLoc)\n xml += ` <video:player_loc>${escapeXml(vid.playerLoc)}</video:player_loc>\\n`;\n if (vid.duration !== undefined)\n xml += ` <video:duration>${vid.duration}</video:duration>\\n`;\n if (vid.expirationDate)\n xml += ` <video:expiration_date>${escapeXml(vid.expirationDate)}</video:expiration_date>\\n`;\n if (vid.rating !== undefined) xml += ` <video:rating>${vid.rating}</video:rating>\\n`;\n if (vid.viewCount !== undefined)\n xml += ` <video:view_count>${vid.viewCount}</video:view_count>\\n`;\n if (vid.publicationDate)\n xml += ` <video:publication_date>${escapeXml(vid.publicationDate)}</video:publication_date>\\n`;\n if (vid.familyFriendly !== undefined)\n xml += ` <video:family_friendly>${vid.familyFriendly ? 'yes' : 'no'}</video:family_friendly>\\n`;\n if (vid.live !== undefined)\n xml += ` <video:live>${vid.live ? 'yes' : 'no'}</video:live>\\n`;\n xml += ' </video:video>\\n';\n return xml;\n })\n .join('');\n}\n\nfunction buildNewsXml(news: SitemapNews): string {\n let xml = ' <news:news>\\n';\n xml += ' <news:publication>\\n';\n xml += ` <news:name>${escapeXml(news.publication.name)}</news:name>\\n`;\n xml += ` <news:language>${escapeXml(news.publication.language)}</news:language>\\n`;\n xml += ' </news:publication>\\n';\n xml += ` <news:publication_date>${escapeXml(news.publicationDate)}</news:publication_date>\\n`;\n xml += ` <news:title>${escapeXml(news.title)}</news:title>\\n`;\n xml += ' </news:news>\\n';\n return xml;\n}\n\nfunction buildUrlXml(url: SitemapURL, hostname: string): string {\n const loc = url.loc.startsWith('http')\n ? url.loc\n : normalizeUrl(`${hostname}${url.loc.startsWith('/') ? '' : '/'}${url.loc}`);\n\n let xml = ' <url>\\n';\n xml += ` <loc>${escapeXml(loc)}</loc>\\n`;\n if (url.lastmod) xml += ` <lastmod>${escapeXml(url.lastmod)}</lastmod>\\n`;\n if (url.changefreq) xml += ` <changefreq>${url.changefreq}</changefreq>\\n`;\n if (url.priority !== undefined) xml += ` <priority>${url.priority.toFixed(1)}</priority>\\n`;\n if (url.images && url.images.length > 0) xml += buildImageXml(url.images);\n if (url.videos && url.videos.length > 0) xml += buildVideoXml(url.videos);\n if (url.news) xml += buildNewsXml(url.news);\n xml += ' </url>\\n';\n return xml;\n}\n\n/**\n * Generate an XML sitemap string from a sitemap configuration.\n *\n * @example\n * ```ts\n * const xml = generateSitemap({\n * hostname: 'https://example.com',\n * urls: [\n * { loc: '/', changefreq: 'daily', priority: 1.0 },\n * { loc: '/about', changefreq: 'monthly', priority: 0.8 },\n * ],\n * });\n * ```\n */\nexport function generateSitemap(config: SitemapConfig): string {\n const { hostname, urls } = config;\n const ns = detectNamespaces(urls);\n\n let xml = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n';\n xml += '<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\"';\n if (ns.image) xml += '\\n xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\"';\n if (ns.video) xml += '\\n xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\"';\n if (ns.news) xml += '\\n xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\"';\n xml += '>\\n';\n\n for (const url of urls) {\n xml += buildUrlXml(url, hostname);\n }\n\n xml += '</urlset>\\n';\n return xml;\n}\n","// @power-seo/sitemap — Types\n// ----------------------------------------------------------------------------\n\nexport type {\n SitemapURL,\n SitemapImage,\n SitemapVideo,\n SitemapNews,\n SitemapConfig,\n} from '@power-seo/core';\n\n/** A sitemap entry in a sitemap index. */\nexport interface SitemapIndexEntry {\n loc: string;\n lastmod?: string;\n}\n\n/** Configuration for the sitemap index generator. */\nexport interface SitemapIndexConfig {\n sitemaps: SitemapIndexEntry[];\n}\n\n/** Result of URL validation. */\nexport interface SitemapValidationResult {\n valid: boolean;\n errors: string[];\n warnings: string[];\n}\n\n/** Maximum URLs allowed per sitemap file. */\nexport const MAX_URLS_PER_SITEMAP = 50_000 as const;\n\n/** Maximum sitemap file size in bytes (50MB). */\nexport const MAX_SITEMAP_SIZE_BYTES = 52_428_800 as const;\n","// @power-seo/sitemap — Sitemap Index Generator\n// ----------------------------------------------------------------------------\n\nimport type { SitemapConfig } from '@power-seo/core';\nimport type { SitemapIndexConfig, SitemapIndexEntry } from './types.js';\nimport { MAX_URLS_PER_SITEMAP } from './types.js';\nimport { generateSitemap } from './generator.js';\n\n/** Escape special XML characters. */\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;');\n}\n\n/**\n * Generate a sitemap index XML string.\n *\n * @example\n * ```ts\n * const indexXml = generateSitemapIndex({\n * sitemaps: [\n * { loc: 'https://example.com/sitemap-0.xml', lastmod: '2024-01-01' },\n * { loc: 'https://example.com/sitemap-1.xml', lastmod: '2024-01-01' },\n * ],\n * });\n * ```\n */\nexport function generateSitemapIndex(config: SitemapIndexConfig): string {\n let xml = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n';\n xml += '<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\\n';\n\n for (const sitemap of config.sitemaps) {\n xml += ' <sitemap>\\n';\n xml += ` <loc>${escapeXml(sitemap.loc)}</loc>\\n`;\n if (sitemap.lastmod) {\n xml += ` <lastmod>${escapeXml(sitemap.lastmod)}</lastmod>\\n`;\n }\n xml += ' </sitemap>\\n';\n }\n\n xml += '</sitemapindex>\\n';\n return xml;\n}\n\n/**\n * Split a large sitemap config into multiple sitemaps + an index.\n *\n * When a site has more than 50,000 URLs, this function splits them into\n * multiple sitemap files and returns both the individual sitemaps and\n * the index that references them.\n *\n * @example\n * ```ts\n * const { index, sitemaps } = splitSitemap({\n * hostname: 'https://example.com',\n * urls: largeUrlArray,\n * });\n * // sitemaps[0].xml, sitemaps[1].xml, etc.\n * // index = sitemap index XML\n * ```\n */\nexport function splitSitemap(\n config: SitemapConfig,\n sitemapUrlPattern = '/sitemap-{index}.xml',\n): { index: string; sitemaps: Array<{ filename: string; xml: string }> } {\n const maxPerSitemap = config.maxUrlsPerSitemap ?? MAX_URLS_PER_SITEMAP;\n const { hostname, urls } = config;\n\n if (urls.length <= maxPerSitemap) {\n const xml = generateSitemap(config);\n const filename = sitemapUrlPattern.replace('{index}', '0');\n const indexEntries: SitemapIndexEntry[] = [{ loc: `${hostname}${filename}` }];\n return {\n index: generateSitemapIndex({ sitemaps: indexEntries }),\n sitemaps: [{ filename, xml }],\n };\n }\n\n const sitemaps: Array<{ filename: string; xml: string }> = [];\n const indexEntries: SitemapIndexEntry[] = [];\n\n for (let i = 0; i < urls.length; i += maxPerSitemap) {\n const chunk = urls.slice(i, i + maxPerSitemap);\n const chunkIndex = Math.floor(i / maxPerSitemap);\n const filename = sitemapUrlPattern.replace('{index}', String(chunkIndex));\n const xml = generateSitemap({ hostname, urls: chunk });\n\n sitemaps.push({ filename, xml });\n indexEntries.push({ loc: `${hostname}${filename}` });\n }\n\n return {\n index: generateSitemapIndex({ sitemaps: indexEntries }),\n sitemaps,\n };\n}\n","// @power-seo/sitemap — Streaming Sitemap Generator\n// ----------------------------------------------------------------------------\n\nimport type { SitemapURL } from '@power-seo/core';\nimport { normalizeUrl } from '@power-seo/core';\n\n/** Escape special XML characters. */\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;');\n}\n\n/**\n * A streaming sitemap generator that yields XML chunks.\n *\n * Useful for server responses where you want to stream the sitemap\n * instead of building the entire XML string in memory.\n *\n * @example\n * ```ts\n * const stream = streamSitemap('https://example.com', urls);\n * for (const chunk of stream) {\n * response.write(chunk);\n * }\n * ```\n */\nexport function* streamSitemap(\n hostname: string,\n urls: Iterable<SitemapURL>,\n): Generator<string, void, undefined> {\n yield '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n';\n yield '<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\"\\n';\n yield ' xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\"\\n';\n yield ' xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\"\\n';\n yield ' xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\">\\n';\n\n for (const url of urls) {\n const loc = url.loc.startsWith('http')\n ? url.loc\n : normalizeUrl(`${hostname}${url.loc.startsWith('/') ? '' : '/'}${url.loc}`);\n\n yield ' <url>\\n';\n yield ` <loc>${escapeXml(loc)}</loc>\\n`;\n\n if (url.lastmod) yield ` <lastmod>${escapeXml(url.lastmod)}</lastmod>\\n`;\n if (url.changefreq) yield ` <changefreq>${url.changefreq}</changefreq>\\n`;\n if (url.priority !== undefined) yield ` <priority>${url.priority.toFixed(1)}</priority>\\n`;\n\n // Images\n if (url.images) {\n for (const img of url.images) {\n yield ' <image:image>\\n';\n yield ` <image:loc>${escapeXml(img.loc)}</image:loc>\\n`;\n if (img.caption) yield ` <image:caption>${escapeXml(img.caption)}</image:caption>\\n`;\n if (img.title) yield ` <image:title>${escapeXml(img.title)}</image:title>\\n`;\n yield ' </image:image>\\n';\n }\n }\n\n // Videos\n if (url.videos) {\n for (const vid of url.videos) {\n yield ' <video:video>\\n';\n yield ` <video:thumbnail_loc>${escapeXml(vid.thumbnailLoc)}</video:thumbnail_loc>\\n`;\n yield ` <video:title>${escapeXml(vid.title)}</video:title>\\n`;\n yield ` <video:description>${escapeXml(vid.description)}</video:description>\\n`;\n if (vid.contentLoc)\n yield ` <video:content_loc>${escapeXml(vid.contentLoc)}</video:content_loc>\\n`;\n if (vid.playerLoc)\n yield ` <video:player_loc>${escapeXml(vid.playerLoc)}</video:player_loc>\\n`;\n yield ' </video:video>\\n';\n }\n }\n\n // News\n if (url.news) {\n yield ' <news:news>\\n';\n yield ' <news:publication>\\n';\n yield ` <news:name>${escapeXml(url.news.publication.name)}</news:name>\\n`;\n yield ` <news:language>${escapeXml(url.news.publication.language)}</news:language>\\n`;\n yield ' </news:publication>\\n';\n yield ` <news:publication_date>${escapeXml(url.news.publicationDate)}</news:publication_date>\\n`;\n yield ` <news:title>${escapeXml(url.news.title)}</news:title>\\n`;\n yield ' </news:news>\\n';\n }\n\n yield ' </url>\\n';\n }\n\n yield '</urlset>\\n';\n}\n","// @power-seo/sitemap — URL Validation\n// ----------------------------------------------------------------------------\n\nimport type { SitemapURL } from '@power-seo/core';\nimport { isAbsoluteUrl, MAX_URL_LENGTH } from '@power-seo/core';\nimport type { SitemapValidationResult } from './types.js';\n\nconst VALID_CHANGEFREQ = ['always', 'hourly', 'daily', 'weekly', 'monthly', 'yearly', 'never'];\n\n/**\n * Validate a sitemap URL entry against the sitemap protocol spec.\n */\nexport function validateSitemapUrl(url: SitemapURL): SitemapValidationResult {\n const errors: string[] = [];\n const warnings: string[] = [];\n\n // loc is required\n if (!url.loc || url.loc.trim().length === 0) {\n errors.push('URL \"loc\" is required and cannot be empty.');\n } else {\n if (!isAbsoluteUrl(url.loc)) {\n errors.push(`URL \"${url.loc}\" must be an absolute URL (starting with http:// or https://).`);\n }\n\n if (url.loc.length > 2048) {\n errors.push(`URL \"${url.loc}\" exceeds the maximum length of 2048 characters.`);\n }\n\n if (url.loc.length > MAX_URL_LENGTH) {\n warnings.push(\n `URL \"${url.loc}\" is ${url.loc.length} characters. URLs under ${MAX_URL_LENGTH} characters are recommended for SEO.`,\n );\n }\n }\n\n // lastmod validation\n if (url.lastmod) {\n const dateRegex = /^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}(:\\d{2})?([+-]\\d{2}:\\d{2}|Z)?)?$/;\n if (!dateRegex.test(url.lastmod)) {\n errors.push(\n `\"lastmod\" value \"${url.lastmod}\" is not a valid W3C datetime format (YYYY-MM-DD or ISO 8601).`,\n );\n }\n }\n\n // changefreq validation\n if (url.changefreq && !VALID_CHANGEFREQ.includes(url.changefreq)) {\n errors.push(\n `\"changefreq\" value \"${url.changefreq}\" is invalid. Must be one of: ${VALID_CHANGEFREQ.join(', ')}.`,\n );\n }\n\n // priority validation\n if (url.priority !== undefined) {\n if (url.priority < 0 || url.priority > 1) {\n errors.push(`\"priority\" value ${url.priority} is out of range. Must be between 0.0 and 1.0.`);\n }\n }\n\n // Image validation\n if (url.images) {\n for (let i = 0; i < url.images.length; i++) {\n const img = url.images[i]!;\n if (!img.loc || img.loc.trim().length === 0) {\n errors.push(`Image ${i + 1}: \"loc\" is required.`);\n } else if (!isAbsoluteUrl(img.loc)) {\n errors.push(`Image ${i + 1}: \"${img.loc}\" must be an absolute URL.`);\n }\n }\n if (url.images.length > 1000) {\n warnings.push(\n `URL has ${url.images.length} images. Google supports up to 1,000 images per page.`,\n );\n }\n }\n\n // Video validation\n if (url.videos) {\n for (let i = 0; i < url.videos.length; i++) {\n const vid = url.videos[i]!;\n if (!vid.title) errors.push(`Video ${i + 1}: \"title\" is required.`);\n if (!vid.description) errors.push(`Video ${i + 1}: \"description\" is required.`);\n if (!vid.thumbnailLoc) errors.push(`Video ${i + 1}: \"thumbnailLoc\" is required.`);\n if (!vid.contentLoc && !vid.playerLoc) {\n errors.push(`Video ${i + 1}: either \"contentLoc\" or \"playerLoc\" must be provided.`);\n }\n if (vid.rating !== undefined && (vid.rating < 0 || vid.rating > 5)) {\n errors.push(`Video ${i + 1}: \"rating\" must be between 0.0 and 5.0.`);\n }\n }\n }\n\n // News validation\n if (url.news) {\n if (!url.news.publication?.name) errors.push('News: \"publication.name\" is required.');\n if (!url.news.publication?.language) errors.push('News: \"publication.language\" is required.');\n if (!url.news.publicationDate) errors.push('News: \"publicationDate\" is required.');\n if (!url.news.title) errors.push('News: \"title\" is required.');\n }\n\n return {\n valid: errors.length === 0,\n errors,\n warnings,\n };\n}\n","// @power-seo/sitemap — Next.js App Router Adapter\n\n\nimport type { SitemapURL } from '@power-seo/core';\nimport { validateSitemapUrl } from './validate.js';\n\n/** Plain object matching the shape of Next.js `MetadataRoute.Sitemap[number]`. */\nexport interface NextSitemapEntry {\n url: string;\n lastModified?: string | Date;\n changeFrequency?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';\n priority?: number;\n}\n\n/**\n * Convert `SitemapURL[]` to a plain array compatible with Next.js `MetadataRoute.Sitemap`.\n *\n * @example\n * ```ts\n * // app/sitemap.ts\n * import { toNextSitemap } from '@power-seo/sitemap';\n * export default async function sitemap() {\n * return toNextSitemap(urls) as MetadataRoute.Sitemap;\n * }\n * ```\n */\nexport function toNextSitemap(urls: SitemapURL[]): NextSitemapEntry[] {\n const entries: NextSitemapEntry[] = [];\n for (const url of urls) {\n const { valid } = validateSitemapUrl(url);\n if (!valid) continue;\n const entry: NextSitemapEntry = { url: url.loc };\n if (url.lastmod) entry.lastModified = url.lastmod;\n if (url.changefreq) entry.changeFrequency = url.changefreq as NextSitemapEntry['changeFrequency'];\n if (url.priority !== undefined) entry.priority = url.priority;\n entries.push(entry);\n }\n return entries;\n}\n"],"mappings":"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","names":["escapeXml","indexEntries","import_core","escapeXml","import_core"]}
package/dist/index.d.cts CHANGED
@@ -97,36 +97,21 @@ declare function streamSitemap(hostname: string, urls: Iterable<SitemapURL>): Ge
97
97
  */
98
98
  declare function validateSitemapUrl(url: SitemapURL): SitemapValidationResult;
99
99
 
100
- /**
101
- * A plain object that matches the shape of Next.js `MetadataRoute.Sitemap[number]`.
102
- * Typed locally so `@power-seo/sitemap` has no dependency on `next`.
103
- */
100
+ /** Plain object matching the shape of Next.js `MetadataRoute.Sitemap[number]`. */
104
101
  interface NextSitemapEntry {
105
102
  url: string;
106
103
  lastModified?: string | Date;
107
104
  changeFrequency?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';
108
105
  priority?: number;
109
- alternates?: {
110
- languages?: Record<string, string>;
111
- };
112
106
  }
113
107
  /**
114
- * Convert an array of `SitemapURL` objects to a Next.js-compatible sitemap array.
115
- *
116
- * Use this in `app/sitemap.ts` to bridge `@power-seo/sitemap` with Next.js's
117
- * built-in `MetadataRoute.Sitemap` convention.
108
+ * Convert `SitemapURL[]` to a plain array compatible with Next.js `MetadataRoute.Sitemap`.
118
109
  *
119
110
  * @example
120
111
  * ```ts
121
112
  * // app/sitemap.ts
122
- * import type { MetadataRoute } from 'next';
123
113
  * import { toNextSitemap } from '@power-seo/sitemap';
124
- *
125
- * export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
126
- * const urls = [
127
- * { loc: 'https://example.com', changefreq: 'daily', priority: 1.0 },
128
- * { loc: 'https://example.com/about', changefreq: 'monthly', priority: 0.8 },
129
- * ];
114
+ * export default async function sitemap() {
130
115
  * return toNextSitemap(urls) as MetadataRoute.Sitemap;
131
116
  * }
132
117
  * ```
package/dist/index.d.ts CHANGED
@@ -97,36 +97,21 @@ declare function streamSitemap(hostname: string, urls: Iterable<SitemapURL>): Ge
97
97
  */
98
98
  declare function validateSitemapUrl(url: SitemapURL): SitemapValidationResult;
99
99
 
100
- /**
101
- * A plain object that matches the shape of Next.js `MetadataRoute.Sitemap[number]`.
102
- * Typed locally so `@power-seo/sitemap` has no dependency on `next`.
103
- */
100
+ /** Plain object matching the shape of Next.js `MetadataRoute.Sitemap[number]`. */
104
101
  interface NextSitemapEntry {
105
102
  url: string;
106
103
  lastModified?: string | Date;
107
104
  changeFrequency?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';
108
105
  priority?: number;
109
- alternates?: {
110
- languages?: Record<string, string>;
111
- };
112
106
  }
113
107
  /**
114
- * Convert an array of `SitemapURL` objects to a Next.js-compatible sitemap array.
115
- *
116
- * Use this in `app/sitemap.ts` to bridge `@power-seo/sitemap` with Next.js's
117
- * built-in `MetadataRoute.Sitemap` convention.
108
+ * Convert `SitemapURL[]` to a plain array compatible with Next.js `MetadataRoute.Sitemap`.
118
109
  *
119
110
  * @example
120
111
  * ```ts
121
112
  * // app/sitemap.ts
122
- * import type { MetadataRoute } from 'next';
123
113
  * import { toNextSitemap } from '@power-seo/sitemap';
124
- *
125
- * export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
126
- * const urls = [
127
- * { loc: 'https://example.com', changefreq: 'daily', priority: 1.0 },
128
- * { loc: 'https://example.com/about', changefreq: 'monthly', priority: 0.8 },
129
- * ];
114
+ * export default async function sitemap() {
130
115
  * return toNextSitemap(urls) as MetadataRoute.Sitemap;
131
116
  * }
132
117
  * ```
package/dist/index.js CHANGED
@@ -331,15 +331,9 @@ function toNextSitemap(urls) {
331
331
  const { valid } = validateSitemapUrl(url);
332
332
  if (!valid) continue;
333
333
  const entry = { url: url.loc };
334
- if (url.lastmod) {
335
- entry.lastModified = url.lastmod;
336
- }
337
- if (url.changefreq) {
338
- entry.changeFrequency = url.changefreq;
339
- }
340
- if (url.priority !== void 0) {
341
- entry.priority = url.priority;
342
- }
334
+ if (url.lastmod) entry.lastModified = url.lastmod;
335
+ if (url.changefreq) entry.changeFrequency = url.changefreq;
336
+ if (url.priority !== void 0) entry.priority = url.priority;
343
337
  entries.push(entry);
344
338
  }
345
339
  return entries;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/generator.ts","../src/types.ts","../src/sitemap-index.ts","../src/stream.ts","../src/validate.ts","../src/next-adapter.ts"],"sourcesContent":["// ============================================================================\n// @power-seo/sitemap — XML Sitemap Generator\n// ============================================================================\n\nimport type {\n SitemapConfig,\n SitemapURL,\n SitemapImage,\n SitemapVideo,\n SitemapNews,\n} from '@power-seo/core';\nimport { normalizeUrl } from '@power-seo/core';\n\n/** Escape special XML characters. */\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;');\n}\n\n/** Determine which namespace extensions are needed. */\nfunction detectNamespaces(urls: SitemapURL[]): { image: boolean; video: boolean; news: boolean } {\n let image = false;\n let video = false;\n let news = false;\n for (const url of urls) {\n if (url.images && url.images.length > 0) image = true;\n if (url.videos && url.videos.length > 0) video = true;\n if (url.news) news = true;\n if (image && video && news) break;\n }\n return { image, video, news };\n}\n\nfunction buildImageXml(images: SitemapImage[]): string {\n return images\n .map((img) => {\n let xml = ' <image:image>\\n';\n xml += ` <image:loc>${escapeXml(img.loc)}</image:loc>\\n`;\n if (img.caption) xml += ` <image:caption>${escapeXml(img.caption)}</image:caption>\\n`;\n if (img.geoLocation)\n xml += ` <image:geo_location>${escapeXml(img.geoLocation)}</image:geo_location>\\n`;\n if (img.title) xml += ` <image:title>${escapeXml(img.title)}</image:title>\\n`;\n if (img.license) xml += ` <image:license>${escapeXml(img.license)}</image:license>\\n`;\n xml += ' </image:image>\\n';\n return xml;\n })\n .join('');\n}\n\nfunction buildVideoXml(videos: SitemapVideo[]): string {\n return videos\n .map((vid) => {\n let xml = ' <video:video>\\n';\n xml += ` <video:thumbnail_loc>${escapeXml(vid.thumbnailLoc)}</video:thumbnail_loc>\\n`;\n xml += ` <video:title>${escapeXml(vid.title)}</video:title>\\n`;\n xml += ` <video:description>${escapeXml(vid.description)}</video:description>\\n`;\n if (vid.contentLoc)\n xml += ` <video:content_loc>${escapeXml(vid.contentLoc)}</video:content_loc>\\n`;\n if (vid.playerLoc)\n xml += ` <video:player_loc>${escapeXml(vid.playerLoc)}</video:player_loc>\\n`;\n if (vid.duration !== undefined)\n xml += ` <video:duration>${vid.duration}</video:duration>\\n`;\n if (vid.expirationDate)\n xml += ` <video:expiration_date>${escapeXml(vid.expirationDate)}</video:expiration_date>\\n`;\n if (vid.rating !== undefined) xml += ` <video:rating>${vid.rating}</video:rating>\\n`;\n if (vid.viewCount !== undefined)\n xml += ` <video:view_count>${vid.viewCount}</video:view_count>\\n`;\n if (vid.publicationDate)\n xml += ` <video:publication_date>${escapeXml(vid.publicationDate)}</video:publication_date>\\n`;\n if (vid.familyFriendly !== undefined)\n xml += ` <video:family_friendly>${vid.familyFriendly ? 'yes' : 'no'}</video:family_friendly>\\n`;\n if (vid.live !== undefined)\n xml += ` <video:live>${vid.live ? 'yes' : 'no'}</video:live>\\n`;\n xml += ' </video:video>\\n';\n return xml;\n })\n .join('');\n}\n\nfunction buildNewsXml(news: SitemapNews): string {\n let xml = ' <news:news>\\n';\n xml += ' <news:publication>\\n';\n xml += ` <news:name>${escapeXml(news.publication.name)}</news:name>\\n`;\n xml += ` <news:language>${escapeXml(news.publication.language)}</news:language>\\n`;\n xml += ' </news:publication>\\n';\n xml += ` <news:publication_date>${escapeXml(news.publicationDate)}</news:publication_date>\\n`;\n xml += ` <news:title>${escapeXml(news.title)}</news:title>\\n`;\n xml += ' </news:news>\\n';\n return xml;\n}\n\nfunction buildUrlXml(url: SitemapURL, hostname: string): string {\n const loc = url.loc.startsWith('http')\n ? url.loc\n : normalizeUrl(`${hostname}${url.loc.startsWith('/') ? '' : '/'}${url.loc}`);\n\n let xml = ' <url>\\n';\n xml += ` <loc>${escapeXml(loc)}</loc>\\n`;\n if (url.lastmod) xml += ` <lastmod>${escapeXml(url.lastmod)}</lastmod>\\n`;\n if (url.changefreq) xml += ` <changefreq>${url.changefreq}</changefreq>\\n`;\n if (url.priority !== undefined) xml += ` <priority>${url.priority.toFixed(1)}</priority>\\n`;\n if (url.images && url.images.length > 0) xml += buildImageXml(url.images);\n if (url.videos && url.videos.length > 0) xml += buildVideoXml(url.videos);\n if (url.news) xml += buildNewsXml(url.news);\n xml += ' </url>\\n';\n return xml;\n}\n\n/**\n * Generate an XML sitemap string from a sitemap configuration.\n *\n * @example\n * ```ts\n * const xml = generateSitemap({\n * hostname: 'https://example.com',\n * urls: [\n * { loc: '/', changefreq: 'daily', priority: 1.0 },\n * { loc: '/about', changefreq: 'monthly', priority: 0.8 },\n * ],\n * });\n * ```\n */\nexport function generateSitemap(config: SitemapConfig): string {\n const { hostname, urls } = config;\n const ns = detectNamespaces(urls);\n\n let xml = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n';\n xml += '<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\"';\n if (ns.image) xml += '\\n xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\"';\n if (ns.video) xml += '\\n xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\"';\n if (ns.news) xml += '\\n xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\"';\n xml += '>\\n';\n\n for (const url of urls) {\n xml += buildUrlXml(url, hostname);\n }\n\n xml += '</urlset>\\n';\n return xml;\n}\n","// ============================================================================\n// @power-seo/sitemap — Types\n// ============================================================================\n\nexport type {\n SitemapURL,\n SitemapImage,\n SitemapVideo,\n SitemapNews,\n SitemapConfig,\n} from '@power-seo/core';\n\n/** A sitemap entry in a sitemap index. */\nexport interface SitemapIndexEntry {\n loc: string;\n lastmod?: string;\n}\n\n/** Configuration for the sitemap index generator. */\nexport interface SitemapIndexConfig {\n sitemaps: SitemapIndexEntry[];\n}\n\n/** Result of URL validation. */\nexport interface SitemapValidationResult {\n valid: boolean;\n errors: string[];\n warnings: string[];\n}\n\n/** Maximum URLs allowed per sitemap file. */\nexport const MAX_URLS_PER_SITEMAP = 50_000 as const;\n\n/** Maximum sitemap file size in bytes (50MB). */\nexport const MAX_SITEMAP_SIZE_BYTES = 52_428_800 as const;\n","// ============================================================================\n// @power-seo/sitemap — Sitemap Index Generator\n// ============================================================================\n\nimport type { SitemapConfig } from '@power-seo/core';\nimport type { SitemapIndexConfig, SitemapIndexEntry } from './types.js';\nimport { MAX_URLS_PER_SITEMAP } from './types.js';\nimport { generateSitemap } from './generator.js';\n\n/** Escape special XML characters. */\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;');\n}\n\n/**\n * Generate a sitemap index XML string.\n *\n * @example\n * ```ts\n * const indexXml = generateSitemapIndex({\n * sitemaps: [\n * { loc: 'https://example.com/sitemap-0.xml', lastmod: '2024-01-01' },\n * { loc: 'https://example.com/sitemap-1.xml', lastmod: '2024-01-01' },\n * ],\n * });\n * ```\n */\nexport function generateSitemapIndex(config: SitemapIndexConfig): string {\n let xml = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n';\n xml += '<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\\n';\n\n for (const sitemap of config.sitemaps) {\n xml += ' <sitemap>\\n';\n xml += ` <loc>${escapeXml(sitemap.loc)}</loc>\\n`;\n if (sitemap.lastmod) {\n xml += ` <lastmod>${escapeXml(sitemap.lastmod)}</lastmod>\\n`;\n }\n xml += ' </sitemap>\\n';\n }\n\n xml += '</sitemapindex>\\n';\n return xml;\n}\n\n/**\n * Split a large sitemap config into multiple sitemaps + an index.\n *\n * When a site has more than 50,000 URLs, this function splits them into\n * multiple sitemap files and returns both the individual sitemaps and\n * the index that references them.\n *\n * @example\n * ```ts\n * const { index, sitemaps } = splitSitemap({\n * hostname: 'https://example.com',\n * urls: largeUrlArray,\n * });\n * // sitemaps[0].xml, sitemaps[1].xml, etc.\n * // index = sitemap index XML\n * ```\n */\nexport function splitSitemap(\n config: SitemapConfig,\n sitemapUrlPattern = '/sitemap-{index}.xml',\n): { index: string; sitemaps: Array<{ filename: string; xml: string }> } {\n const maxPerSitemap = config.maxUrlsPerSitemap ?? MAX_URLS_PER_SITEMAP;\n const { hostname, urls } = config;\n\n if (urls.length <= maxPerSitemap) {\n const xml = generateSitemap(config);\n const filename = sitemapUrlPattern.replace('{index}', '0');\n const indexEntries: SitemapIndexEntry[] = [{ loc: `${hostname}${filename}` }];\n return {\n index: generateSitemapIndex({ sitemaps: indexEntries }),\n sitemaps: [{ filename, xml }],\n };\n }\n\n const sitemaps: Array<{ filename: string; xml: string }> = [];\n const indexEntries: SitemapIndexEntry[] = [];\n\n for (let i = 0; i < urls.length; i += maxPerSitemap) {\n const chunk = urls.slice(i, i + maxPerSitemap);\n const chunkIndex = Math.floor(i / maxPerSitemap);\n const filename = sitemapUrlPattern.replace('{index}', String(chunkIndex));\n const xml = generateSitemap({ hostname, urls: chunk });\n\n sitemaps.push({ filename, xml });\n indexEntries.push({ loc: `${hostname}${filename}` });\n }\n\n return {\n index: generateSitemapIndex({ sitemaps: indexEntries }),\n sitemaps,\n };\n}\n","// ============================================================================\n// @power-seo/sitemap — Streaming Sitemap Generator\n// ============================================================================\n\nimport type { SitemapURL } from '@power-seo/core';\nimport { normalizeUrl } from '@power-seo/core';\n\n/** Escape special XML characters. */\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;');\n}\n\n/**\n * A streaming sitemap generator that yields XML chunks.\n *\n * Useful for server responses where you want to stream the sitemap\n * instead of building the entire XML string in memory.\n *\n * @example\n * ```ts\n * const stream = streamSitemap('https://example.com', urls);\n * for (const chunk of stream) {\n * response.write(chunk);\n * }\n * ```\n */\nexport function* streamSitemap(\n hostname: string,\n urls: Iterable<SitemapURL>,\n): Generator<string, void, undefined> {\n yield '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n';\n yield '<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\"\\n';\n yield ' xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\"\\n';\n yield ' xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\"\\n';\n yield ' xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\">\\n';\n\n for (const url of urls) {\n const loc = url.loc.startsWith('http')\n ? url.loc\n : normalizeUrl(`${hostname}${url.loc.startsWith('/') ? '' : '/'}${url.loc}`);\n\n yield ' <url>\\n';\n yield ` <loc>${escapeXml(loc)}</loc>\\n`;\n\n if (url.lastmod) yield ` <lastmod>${escapeXml(url.lastmod)}</lastmod>\\n`;\n if (url.changefreq) yield ` <changefreq>${url.changefreq}</changefreq>\\n`;\n if (url.priority !== undefined) yield ` <priority>${url.priority.toFixed(1)}</priority>\\n`;\n\n // Images\n if (url.images) {\n for (const img of url.images) {\n yield ' <image:image>\\n';\n yield ` <image:loc>${escapeXml(img.loc)}</image:loc>\\n`;\n if (img.caption) yield ` <image:caption>${escapeXml(img.caption)}</image:caption>\\n`;\n if (img.title) yield ` <image:title>${escapeXml(img.title)}</image:title>\\n`;\n yield ' </image:image>\\n';\n }\n }\n\n // Videos\n if (url.videos) {\n for (const vid of url.videos) {\n yield ' <video:video>\\n';\n yield ` <video:thumbnail_loc>${escapeXml(vid.thumbnailLoc)}</video:thumbnail_loc>\\n`;\n yield ` <video:title>${escapeXml(vid.title)}</video:title>\\n`;\n yield ` <video:description>${escapeXml(vid.description)}</video:description>\\n`;\n if (vid.contentLoc)\n yield ` <video:content_loc>${escapeXml(vid.contentLoc)}</video:content_loc>\\n`;\n if (vid.playerLoc)\n yield ` <video:player_loc>${escapeXml(vid.playerLoc)}</video:player_loc>\\n`;\n yield ' </video:video>\\n';\n }\n }\n\n // News\n if (url.news) {\n yield ' <news:news>\\n';\n yield ' <news:publication>\\n';\n yield ` <news:name>${escapeXml(url.news.publication.name)}</news:name>\\n`;\n yield ` <news:language>${escapeXml(url.news.publication.language)}</news:language>\\n`;\n yield ' </news:publication>\\n';\n yield ` <news:publication_date>${escapeXml(url.news.publicationDate)}</news:publication_date>\\n`;\n yield ` <news:title>${escapeXml(url.news.title)}</news:title>\\n`;\n yield ' </news:news>\\n';\n }\n\n yield ' </url>\\n';\n }\n\n yield '</urlset>\\n';\n}\n","// ============================================================================\n// @power-seo/sitemap — URL Validation\n// ============================================================================\n\nimport type { SitemapURL } from '@power-seo/core';\nimport { isAbsoluteUrl, MAX_URL_LENGTH } from '@power-seo/core';\nimport type { SitemapValidationResult } from './types.js';\n\nconst VALID_CHANGEFREQ = ['always', 'hourly', 'daily', 'weekly', 'monthly', 'yearly', 'never'];\n\n/**\n * Validate a sitemap URL entry against the sitemap protocol spec.\n */\nexport function validateSitemapUrl(url: SitemapURL): SitemapValidationResult {\n const errors: string[] = [];\n const warnings: string[] = [];\n\n // loc is required\n if (!url.loc || url.loc.trim().length === 0) {\n errors.push('URL \"loc\" is required and cannot be empty.');\n } else {\n if (!isAbsoluteUrl(url.loc)) {\n errors.push(`URL \"${url.loc}\" must be an absolute URL (starting with http:// or https://).`);\n }\n\n if (url.loc.length > 2048) {\n errors.push(`URL \"${url.loc}\" exceeds the maximum length of 2048 characters.`);\n }\n\n if (url.loc.length > MAX_URL_LENGTH) {\n warnings.push(\n `URL \"${url.loc}\" is ${url.loc.length} characters. URLs under ${MAX_URL_LENGTH} characters are recommended for SEO.`,\n );\n }\n }\n\n // lastmod validation\n if (url.lastmod) {\n const dateRegex = /^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}(:\\d{2})?([+-]\\d{2}:\\d{2}|Z)?)?$/;\n if (!dateRegex.test(url.lastmod)) {\n errors.push(\n `\"lastmod\" value \"${url.lastmod}\" is not a valid W3C datetime format (YYYY-MM-DD or ISO 8601).`,\n );\n }\n }\n\n // changefreq validation\n if (url.changefreq && !VALID_CHANGEFREQ.includes(url.changefreq)) {\n errors.push(\n `\"changefreq\" value \"${url.changefreq}\" is invalid. Must be one of: ${VALID_CHANGEFREQ.join(', ')}.`,\n );\n }\n\n // priority validation\n if (url.priority !== undefined) {\n if (url.priority < 0 || url.priority > 1) {\n errors.push(`\"priority\" value ${url.priority} is out of range. Must be between 0.0 and 1.0.`);\n }\n }\n\n // Image validation\n if (url.images) {\n for (let i = 0; i < url.images.length; i++) {\n const img = url.images[i]!;\n if (!img.loc || img.loc.trim().length === 0) {\n errors.push(`Image ${i + 1}: \"loc\" is required.`);\n } else if (!isAbsoluteUrl(img.loc)) {\n errors.push(`Image ${i + 1}: \"${img.loc}\" must be an absolute URL.`);\n }\n }\n if (url.images.length > 1000) {\n warnings.push(\n `URL has ${url.images.length} images. Google supports up to 1,000 images per page.`,\n );\n }\n }\n\n // Video validation\n if (url.videos) {\n for (let i = 0; i < url.videos.length; i++) {\n const vid = url.videos[i]!;\n if (!vid.title) errors.push(`Video ${i + 1}: \"title\" is required.`);\n if (!vid.description) errors.push(`Video ${i + 1}: \"description\" is required.`);\n if (!vid.thumbnailLoc) errors.push(`Video ${i + 1}: \"thumbnailLoc\" is required.`);\n if (!vid.contentLoc && !vid.playerLoc) {\n errors.push(`Video ${i + 1}: either \"contentLoc\" or \"playerLoc\" must be provided.`);\n }\n if (vid.rating !== undefined && (vid.rating < 0 || vid.rating > 5)) {\n errors.push(`Video ${i + 1}: \"rating\" must be between 0.0 and 5.0.`);\n }\n }\n }\n\n // News validation\n if (url.news) {\n if (!url.news.publication?.name) errors.push('News: \"publication.name\" is required.');\n if (!url.news.publication?.language) errors.push('News: \"publication.language\" is required.');\n if (!url.news.publicationDate) errors.push('News: \"publicationDate\" is required.');\n if (!url.news.title) errors.push('News: \"title\" is required.');\n }\n\n return {\n valid: errors.length === 0,\n errors,\n warnings,\n };\n}\n","// ============================================================================\n// @power-seo/sitemap — Next.js App Router Adapter\n// ============================================================================\n\nimport type { SitemapURL } from '@power-seo/core';\nimport { validateSitemapUrl } from './validate.js';\n\n/**\n * A plain object that matches the shape of Next.js `MetadataRoute.Sitemap[number]`.\n * Typed locally so `@power-seo/sitemap` has no dependency on `next`.\n */\nexport interface NextSitemapEntry {\n url: string;\n lastModified?: string | Date;\n changeFrequency?:\n | 'always'\n | 'hourly'\n | 'daily'\n | 'weekly'\n | 'monthly'\n | 'yearly'\n | 'never';\n priority?: number;\n alternates?: {\n languages?: Record<string, string>;\n };\n}\n\n/**\n * Convert an array of `SitemapURL` objects to a Next.js-compatible sitemap array.\n *\n * Use this in `app/sitemap.ts` to bridge `@power-seo/sitemap` with Next.js's\n * built-in `MetadataRoute.Sitemap` convention.\n *\n * @example\n * ```ts\n * // app/sitemap.ts\n * import type { MetadataRoute } from 'next';\n * import { toNextSitemap } from '@power-seo/sitemap';\n *\n * export default async function sitemap(): Promise<MetadataRoute.Sitemap> {\n * const urls = [\n * { loc: 'https://example.com', changefreq: 'daily', priority: 1.0 },\n * { loc: 'https://example.com/about', changefreq: 'monthly', priority: 0.8 },\n * ];\n * return toNextSitemap(urls) as MetadataRoute.Sitemap;\n * }\n * ```\n */\nexport function toNextSitemap(urls: SitemapURL[]): NextSitemapEntry[] {\n const entries: NextSitemapEntry[] = [];\n\n for (const url of urls) {\n const { valid } = validateSitemapUrl(url);\n if (!valid) continue;\n\n const entry: NextSitemapEntry = { url: url.loc };\n\n if (url.lastmod) {\n entry.lastModified = url.lastmod;\n }\n\n if (url.changefreq) {\n entry.changeFrequency = url.changefreq as NextSitemapEntry['changeFrequency'];\n }\n\n if (url.priority !== undefined) {\n entry.priority = url.priority;\n }\n\n entries.push(entry);\n }\n\n return entries;\n}\n"],"mappings":"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","names":["escapeXml","indexEntries","normalizeUrl","escapeXml"]}
1
+ {"version":3,"sources":["../src/generator.ts","../src/types.ts","../src/sitemap-index.ts","../src/stream.ts","../src/validate.ts","../src/next-adapter.ts"],"sourcesContent":["// @power-seo/sitemap — XML Sitemap Generator\n// ----------------------------------------------------------------------------\n\nimport type {\n SitemapConfig,\n SitemapURL,\n SitemapImage,\n SitemapVideo,\n SitemapNews,\n} from '@power-seo/core';\nimport { normalizeUrl } from '@power-seo/core';\n\n/** Escape special XML characters. */\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;');\n}\n\n/** Determine which namespace extensions are needed. */\nfunction detectNamespaces(urls: SitemapURL[]): { image: boolean; video: boolean; news: boolean } {\n let image = false;\n let video = false;\n let news = false;\n for (const url of urls) {\n if (url.images && url.images.length > 0) image = true;\n if (url.videos && url.videos.length > 0) video = true;\n if (url.news) news = true;\n if (image && video && news) break;\n }\n return { image, video, news };\n}\n\nfunction buildImageXml(images: SitemapImage[]): string {\n return images\n .map((img) => {\n let xml = ' <image:image>\\n';\n xml += ` <image:loc>${escapeXml(img.loc)}</image:loc>\\n`;\n if (img.caption) xml += ` <image:caption>${escapeXml(img.caption)}</image:caption>\\n`;\n if (img.geoLocation)\n xml += ` <image:geo_location>${escapeXml(img.geoLocation)}</image:geo_location>\\n`;\n if (img.title) xml += ` <image:title>${escapeXml(img.title)}</image:title>\\n`;\n if (img.license) xml += ` <image:license>${escapeXml(img.license)}</image:license>\\n`;\n xml += ' </image:image>\\n';\n return xml;\n })\n .join('');\n}\n\nfunction buildVideoXml(videos: SitemapVideo[]): string {\n return videos\n .map((vid) => {\n let xml = ' <video:video>\\n';\n xml += ` <video:thumbnail_loc>${escapeXml(vid.thumbnailLoc)}</video:thumbnail_loc>\\n`;\n xml += ` <video:title>${escapeXml(vid.title)}</video:title>\\n`;\n xml += ` <video:description>${escapeXml(vid.description)}</video:description>\\n`;\n if (vid.contentLoc)\n xml += ` <video:content_loc>${escapeXml(vid.contentLoc)}</video:content_loc>\\n`;\n if (vid.playerLoc)\n xml += ` <video:player_loc>${escapeXml(vid.playerLoc)}</video:player_loc>\\n`;\n if (vid.duration !== undefined)\n xml += ` <video:duration>${vid.duration}</video:duration>\\n`;\n if (vid.expirationDate)\n xml += ` <video:expiration_date>${escapeXml(vid.expirationDate)}</video:expiration_date>\\n`;\n if (vid.rating !== undefined) xml += ` <video:rating>${vid.rating}</video:rating>\\n`;\n if (vid.viewCount !== undefined)\n xml += ` <video:view_count>${vid.viewCount}</video:view_count>\\n`;\n if (vid.publicationDate)\n xml += ` <video:publication_date>${escapeXml(vid.publicationDate)}</video:publication_date>\\n`;\n if (vid.familyFriendly !== undefined)\n xml += ` <video:family_friendly>${vid.familyFriendly ? 'yes' : 'no'}</video:family_friendly>\\n`;\n if (vid.live !== undefined)\n xml += ` <video:live>${vid.live ? 'yes' : 'no'}</video:live>\\n`;\n xml += ' </video:video>\\n';\n return xml;\n })\n .join('');\n}\n\nfunction buildNewsXml(news: SitemapNews): string {\n let xml = ' <news:news>\\n';\n xml += ' <news:publication>\\n';\n xml += ` <news:name>${escapeXml(news.publication.name)}</news:name>\\n`;\n xml += ` <news:language>${escapeXml(news.publication.language)}</news:language>\\n`;\n xml += ' </news:publication>\\n';\n xml += ` <news:publication_date>${escapeXml(news.publicationDate)}</news:publication_date>\\n`;\n xml += ` <news:title>${escapeXml(news.title)}</news:title>\\n`;\n xml += ' </news:news>\\n';\n return xml;\n}\n\nfunction buildUrlXml(url: SitemapURL, hostname: string): string {\n const loc = url.loc.startsWith('http')\n ? url.loc\n : normalizeUrl(`${hostname}${url.loc.startsWith('/') ? '' : '/'}${url.loc}`);\n\n let xml = ' <url>\\n';\n xml += ` <loc>${escapeXml(loc)}</loc>\\n`;\n if (url.lastmod) xml += ` <lastmod>${escapeXml(url.lastmod)}</lastmod>\\n`;\n if (url.changefreq) xml += ` <changefreq>${url.changefreq}</changefreq>\\n`;\n if (url.priority !== undefined) xml += ` <priority>${url.priority.toFixed(1)}</priority>\\n`;\n if (url.images && url.images.length > 0) xml += buildImageXml(url.images);\n if (url.videos && url.videos.length > 0) xml += buildVideoXml(url.videos);\n if (url.news) xml += buildNewsXml(url.news);\n xml += ' </url>\\n';\n return xml;\n}\n\n/**\n * Generate an XML sitemap string from a sitemap configuration.\n *\n * @example\n * ```ts\n * const xml = generateSitemap({\n * hostname: 'https://example.com',\n * urls: [\n * { loc: '/', changefreq: 'daily', priority: 1.0 },\n * { loc: '/about', changefreq: 'monthly', priority: 0.8 },\n * ],\n * });\n * ```\n */\nexport function generateSitemap(config: SitemapConfig): string {\n const { hostname, urls } = config;\n const ns = detectNamespaces(urls);\n\n let xml = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n';\n xml += '<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\"';\n if (ns.image) xml += '\\n xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\"';\n if (ns.video) xml += '\\n xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\"';\n if (ns.news) xml += '\\n xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\"';\n xml += '>\\n';\n\n for (const url of urls) {\n xml += buildUrlXml(url, hostname);\n }\n\n xml += '</urlset>\\n';\n return xml;\n}\n","// @power-seo/sitemap — Types\n// ----------------------------------------------------------------------------\n\nexport type {\n SitemapURL,\n SitemapImage,\n SitemapVideo,\n SitemapNews,\n SitemapConfig,\n} from '@power-seo/core';\n\n/** A sitemap entry in a sitemap index. */\nexport interface SitemapIndexEntry {\n loc: string;\n lastmod?: string;\n}\n\n/** Configuration for the sitemap index generator. */\nexport interface SitemapIndexConfig {\n sitemaps: SitemapIndexEntry[];\n}\n\n/** Result of URL validation. */\nexport interface SitemapValidationResult {\n valid: boolean;\n errors: string[];\n warnings: string[];\n}\n\n/** Maximum URLs allowed per sitemap file. */\nexport const MAX_URLS_PER_SITEMAP = 50_000 as const;\n\n/** Maximum sitemap file size in bytes (50MB). */\nexport const MAX_SITEMAP_SIZE_BYTES = 52_428_800 as const;\n","// @power-seo/sitemap — Sitemap Index Generator\n// ----------------------------------------------------------------------------\n\nimport type { SitemapConfig } from '@power-seo/core';\nimport type { SitemapIndexConfig, SitemapIndexEntry } from './types.js';\nimport { MAX_URLS_PER_SITEMAP } from './types.js';\nimport { generateSitemap } from './generator.js';\n\n/** Escape special XML characters. */\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;');\n}\n\n/**\n * Generate a sitemap index XML string.\n *\n * @example\n * ```ts\n * const indexXml = generateSitemapIndex({\n * sitemaps: [\n * { loc: 'https://example.com/sitemap-0.xml', lastmod: '2024-01-01' },\n * { loc: 'https://example.com/sitemap-1.xml', lastmod: '2024-01-01' },\n * ],\n * });\n * ```\n */\nexport function generateSitemapIndex(config: SitemapIndexConfig): string {\n let xml = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n';\n xml += '<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\\n';\n\n for (const sitemap of config.sitemaps) {\n xml += ' <sitemap>\\n';\n xml += ` <loc>${escapeXml(sitemap.loc)}</loc>\\n`;\n if (sitemap.lastmod) {\n xml += ` <lastmod>${escapeXml(sitemap.lastmod)}</lastmod>\\n`;\n }\n xml += ' </sitemap>\\n';\n }\n\n xml += '</sitemapindex>\\n';\n return xml;\n}\n\n/**\n * Split a large sitemap config into multiple sitemaps + an index.\n *\n * When a site has more than 50,000 URLs, this function splits them into\n * multiple sitemap files and returns both the individual sitemaps and\n * the index that references them.\n *\n * @example\n * ```ts\n * const { index, sitemaps } = splitSitemap({\n * hostname: 'https://example.com',\n * urls: largeUrlArray,\n * });\n * // sitemaps[0].xml, sitemaps[1].xml, etc.\n * // index = sitemap index XML\n * ```\n */\nexport function splitSitemap(\n config: SitemapConfig,\n sitemapUrlPattern = '/sitemap-{index}.xml',\n): { index: string; sitemaps: Array<{ filename: string; xml: string }> } {\n const maxPerSitemap = config.maxUrlsPerSitemap ?? MAX_URLS_PER_SITEMAP;\n const { hostname, urls } = config;\n\n if (urls.length <= maxPerSitemap) {\n const xml = generateSitemap(config);\n const filename = sitemapUrlPattern.replace('{index}', '0');\n const indexEntries: SitemapIndexEntry[] = [{ loc: `${hostname}${filename}` }];\n return {\n index: generateSitemapIndex({ sitemaps: indexEntries }),\n sitemaps: [{ filename, xml }],\n };\n }\n\n const sitemaps: Array<{ filename: string; xml: string }> = [];\n const indexEntries: SitemapIndexEntry[] = [];\n\n for (let i = 0; i < urls.length; i += maxPerSitemap) {\n const chunk = urls.slice(i, i + maxPerSitemap);\n const chunkIndex = Math.floor(i / maxPerSitemap);\n const filename = sitemapUrlPattern.replace('{index}', String(chunkIndex));\n const xml = generateSitemap({ hostname, urls: chunk });\n\n sitemaps.push({ filename, xml });\n indexEntries.push({ loc: `${hostname}${filename}` });\n }\n\n return {\n index: generateSitemapIndex({ sitemaps: indexEntries }),\n sitemaps,\n };\n}\n","// @power-seo/sitemap — Streaming Sitemap Generator\n// ----------------------------------------------------------------------------\n\nimport type { SitemapURL } from '@power-seo/core';\nimport { normalizeUrl } from '@power-seo/core';\n\n/** Escape special XML characters. */\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;');\n}\n\n/**\n * A streaming sitemap generator that yields XML chunks.\n *\n * Useful for server responses where you want to stream the sitemap\n * instead of building the entire XML string in memory.\n *\n * @example\n * ```ts\n * const stream = streamSitemap('https://example.com', urls);\n * for (const chunk of stream) {\n * response.write(chunk);\n * }\n * ```\n */\nexport function* streamSitemap(\n hostname: string,\n urls: Iterable<SitemapURL>,\n): Generator<string, void, undefined> {\n yield '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n';\n yield '<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\"\\n';\n yield ' xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\"\\n';\n yield ' xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\"\\n';\n yield ' xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\">\\n';\n\n for (const url of urls) {\n const loc = url.loc.startsWith('http')\n ? url.loc\n : normalizeUrl(`${hostname}${url.loc.startsWith('/') ? '' : '/'}${url.loc}`);\n\n yield ' <url>\\n';\n yield ` <loc>${escapeXml(loc)}</loc>\\n`;\n\n if (url.lastmod) yield ` <lastmod>${escapeXml(url.lastmod)}</lastmod>\\n`;\n if (url.changefreq) yield ` <changefreq>${url.changefreq}</changefreq>\\n`;\n if (url.priority !== undefined) yield ` <priority>${url.priority.toFixed(1)}</priority>\\n`;\n\n // Images\n if (url.images) {\n for (const img of url.images) {\n yield ' <image:image>\\n';\n yield ` <image:loc>${escapeXml(img.loc)}</image:loc>\\n`;\n if (img.caption) yield ` <image:caption>${escapeXml(img.caption)}</image:caption>\\n`;\n if (img.title) yield ` <image:title>${escapeXml(img.title)}</image:title>\\n`;\n yield ' </image:image>\\n';\n }\n }\n\n // Videos\n if (url.videos) {\n for (const vid of url.videos) {\n yield ' <video:video>\\n';\n yield ` <video:thumbnail_loc>${escapeXml(vid.thumbnailLoc)}</video:thumbnail_loc>\\n`;\n yield ` <video:title>${escapeXml(vid.title)}</video:title>\\n`;\n yield ` <video:description>${escapeXml(vid.description)}</video:description>\\n`;\n if (vid.contentLoc)\n yield ` <video:content_loc>${escapeXml(vid.contentLoc)}</video:content_loc>\\n`;\n if (vid.playerLoc)\n yield ` <video:player_loc>${escapeXml(vid.playerLoc)}</video:player_loc>\\n`;\n yield ' </video:video>\\n';\n }\n }\n\n // News\n if (url.news) {\n yield ' <news:news>\\n';\n yield ' <news:publication>\\n';\n yield ` <news:name>${escapeXml(url.news.publication.name)}</news:name>\\n`;\n yield ` <news:language>${escapeXml(url.news.publication.language)}</news:language>\\n`;\n yield ' </news:publication>\\n';\n yield ` <news:publication_date>${escapeXml(url.news.publicationDate)}</news:publication_date>\\n`;\n yield ` <news:title>${escapeXml(url.news.title)}</news:title>\\n`;\n yield ' </news:news>\\n';\n }\n\n yield ' </url>\\n';\n }\n\n yield '</urlset>\\n';\n}\n","// @power-seo/sitemap — URL Validation\n// ----------------------------------------------------------------------------\n\nimport type { SitemapURL } from '@power-seo/core';\nimport { isAbsoluteUrl, MAX_URL_LENGTH } from '@power-seo/core';\nimport type { SitemapValidationResult } from './types.js';\n\nconst VALID_CHANGEFREQ = ['always', 'hourly', 'daily', 'weekly', 'monthly', 'yearly', 'never'];\n\n/**\n * Validate a sitemap URL entry against the sitemap protocol spec.\n */\nexport function validateSitemapUrl(url: SitemapURL): SitemapValidationResult {\n const errors: string[] = [];\n const warnings: string[] = [];\n\n // loc is required\n if (!url.loc || url.loc.trim().length === 0) {\n errors.push('URL \"loc\" is required and cannot be empty.');\n } else {\n if (!isAbsoluteUrl(url.loc)) {\n errors.push(`URL \"${url.loc}\" must be an absolute URL (starting with http:// or https://).`);\n }\n\n if (url.loc.length > 2048) {\n errors.push(`URL \"${url.loc}\" exceeds the maximum length of 2048 characters.`);\n }\n\n if (url.loc.length > MAX_URL_LENGTH) {\n warnings.push(\n `URL \"${url.loc}\" is ${url.loc.length} characters. URLs under ${MAX_URL_LENGTH} characters are recommended for SEO.`,\n );\n }\n }\n\n // lastmod validation\n if (url.lastmod) {\n const dateRegex = /^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}(:\\d{2})?([+-]\\d{2}:\\d{2}|Z)?)?$/;\n if (!dateRegex.test(url.lastmod)) {\n errors.push(\n `\"lastmod\" value \"${url.lastmod}\" is not a valid W3C datetime format (YYYY-MM-DD or ISO 8601).`,\n );\n }\n }\n\n // changefreq validation\n if (url.changefreq && !VALID_CHANGEFREQ.includes(url.changefreq)) {\n errors.push(\n `\"changefreq\" value \"${url.changefreq}\" is invalid. Must be one of: ${VALID_CHANGEFREQ.join(', ')}.`,\n );\n }\n\n // priority validation\n if (url.priority !== undefined) {\n if (url.priority < 0 || url.priority > 1) {\n errors.push(`\"priority\" value ${url.priority} is out of range. Must be between 0.0 and 1.0.`);\n }\n }\n\n // Image validation\n if (url.images) {\n for (let i = 0; i < url.images.length; i++) {\n const img = url.images[i]!;\n if (!img.loc || img.loc.trim().length === 0) {\n errors.push(`Image ${i + 1}: \"loc\" is required.`);\n } else if (!isAbsoluteUrl(img.loc)) {\n errors.push(`Image ${i + 1}: \"${img.loc}\" must be an absolute URL.`);\n }\n }\n if (url.images.length > 1000) {\n warnings.push(\n `URL has ${url.images.length} images. Google supports up to 1,000 images per page.`,\n );\n }\n }\n\n // Video validation\n if (url.videos) {\n for (let i = 0; i < url.videos.length; i++) {\n const vid = url.videos[i]!;\n if (!vid.title) errors.push(`Video ${i + 1}: \"title\" is required.`);\n if (!vid.description) errors.push(`Video ${i + 1}: \"description\" is required.`);\n if (!vid.thumbnailLoc) errors.push(`Video ${i + 1}: \"thumbnailLoc\" is required.`);\n if (!vid.contentLoc && !vid.playerLoc) {\n errors.push(`Video ${i + 1}: either \"contentLoc\" or \"playerLoc\" must be provided.`);\n }\n if (vid.rating !== undefined && (vid.rating < 0 || vid.rating > 5)) {\n errors.push(`Video ${i + 1}: \"rating\" must be between 0.0 and 5.0.`);\n }\n }\n }\n\n // News validation\n if (url.news) {\n if (!url.news.publication?.name) errors.push('News: \"publication.name\" is required.');\n if (!url.news.publication?.language) errors.push('News: \"publication.language\" is required.');\n if (!url.news.publicationDate) errors.push('News: \"publicationDate\" is required.');\n if (!url.news.title) errors.push('News: \"title\" is required.');\n }\n\n return {\n valid: errors.length === 0,\n errors,\n warnings,\n };\n}\n","// @power-seo/sitemap — Next.js App Router Adapter\n\n\nimport type { SitemapURL } from '@power-seo/core';\nimport { validateSitemapUrl } from './validate.js';\n\n/** Plain object matching the shape of Next.js `MetadataRoute.Sitemap[number]`. */\nexport interface NextSitemapEntry {\n url: string;\n lastModified?: string | Date;\n changeFrequency?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';\n priority?: number;\n}\n\n/**\n * Convert `SitemapURL[]` to a plain array compatible with Next.js `MetadataRoute.Sitemap`.\n *\n * @example\n * ```ts\n * // app/sitemap.ts\n * import { toNextSitemap } from '@power-seo/sitemap';\n * export default async function sitemap() {\n * return toNextSitemap(urls) as MetadataRoute.Sitemap;\n * }\n * ```\n */\nexport function toNextSitemap(urls: SitemapURL[]): NextSitemapEntry[] {\n const entries: NextSitemapEntry[] = [];\n for (const url of urls) {\n const { valid } = validateSitemapUrl(url);\n if (!valid) continue;\n const entry: NextSitemapEntry = { url: url.loc };\n if (url.lastmod) entry.lastModified = url.lastmod;\n if (url.changefreq) entry.changeFrequency = url.changefreq as NextSitemapEntry['changeFrequency'];\n if (url.priority !== undefined) entry.priority = url.priority;\n entries.push(entry);\n }\n return entries;\n}\n"],"mappings":"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","names":["escapeXml","indexEntries","normalizeUrl","escapeXml"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@power-seo/sitemap",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "XML sitemap generation, streaming, and validation with image, video, and news support",
5
5
  "license": "MIT",
6
6
  "type": "module",