next-advanced-sitemap 1.1.9 → 1.2.0

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/README.md CHANGED
@@ -3,14 +3,15 @@
3
3
  [![License: FPL](https://img.shields.io/badge/License-FPL-orange.svg)](LICENSE)
4
4
  ![CI Status](https://github.com/fomadev/next-advanced-sitemap/actions/workflows/tests.yml/badge.svg)
5
5
 
6
- A robust and type-safe sitemap generator for Next.js (App Router). This library extends standard sitemap capabilities by providing native support for Google-specific metadata including Images, Videos, News, and Internationalization (Hreflang).
6
+ A robust and type-safe sitemap generator for Next.js (App Router). This library extends standard sitemap capabilities by providing native support for Google-specific metadata including Images, Videos, News, Internationalization (Hreflang), and large-scale Sitemap Index structures.
7
7
 
8
8
  ## Overview
9
9
 
10
- While Next.js provides a built-in `MetadataRoute.Sitemap` utility, it currently lacks support for advanced SEO attributes required by high-performance web applications. `next-advanced-sitemap` bridges this gap, allowing developers to programmatically generate complex XML sitemaps that comply with Google's extended schemas.
10
+ While Next.js provides a built-in `MetadataRoute.Sitemap` utility, it currently lacks support for advanced SEO attributes required by high-performance web applications. `next-advanced-sitemap` bridges this gap, allowing developers to programmatically generate complex XML sitemaps and indexes that comply with Google's extended schemas.
11
11
 
12
12
  ## Features
13
13
 
14
+ - **Native Sitemap Indexing Architecture (v1.2.0)**: Advanced support for sitemap index grouping (`getServerSitemapIndexResponse`). Allows seamless scaling by linking multiple sub-sitemaps (e.g., `sitemap-0.xml`, `sitemap-products.xml`) under a centralized endpoint to bypass Google's 50,000 URLs strict limitation.
14
15
  - **Cross-Field Semantic Validation (v1.1.9)**: Native cross-field validation engine that intercepts logical data contradictions (e.g., Live streams with static durations, subscriptions conflicts, or expired news) before writing the XML stream. Guarantees a flawless 100% compliance score in Google Search Console.
15
16
  - **Financial Google News Syndication (v1.1.8)**: Native support for `<news:stock_tickers>` tags, mapping general press articles directly to active global stock market boards.
16
17
  - **Video Semantic Classification & Long-Tail SEO (v1.1.7)**: Support for `<video:category>` and multiple `<video:tag>` elements to deeply contextualize video content and map assets to highly targeted niche queries.
@@ -42,7 +43,9 @@ npm install next-advanced-sitemap
42
43
 
43
44
  ## Usage
44
45
 
45
- To implement an advanced sitemap in the Next.js App Router, create a Route Handler at `app/sitemap.xml/route.ts`.
46
+ ### 1. Generating a Sub-Sitemap (Standard XML)
47
+
48
+ To implement a rich structural sitemap in the Next.js App Router, create a Route Handler at `app/sitemap-records.xml/route.ts`.
46
49
 
47
50
  ```typescript
48
51
  import { getServerSitemapResponse, SitemapEntry } from 'next-advanced-sitemap';
@@ -70,22 +73,13 @@ export async function GET() {
70
73
  publication_date: new Date(),
71
74
  duration: 7200,
72
75
  view_count: 25000,
73
- // v1.1.7: Semantic Topical Classification & Long-Tail Tags
74
- category: ' Education & Technology ', // Auto-trimmed and XML-escaped
75
- tags: ['nextjs', 'typescript', ' advanced seo '], // Limited to 32 tags max
76
- // v1.1.6: Commercial VOD Pricing Structure (Auto ISO 4217 & decimal formatting)
76
+ category: 'Education & Technology',
77
+ tags: ['nextjs', 'typescript', 'advanced seo'],
77
78
  price: { value: 19.99, currency: 'usd', type: 'own' },
78
- // v1.1.5: Flexible Paywall Registration (Accepts boolean or strict 'yes' | 'no')
79
79
  requires_subscription: true,
80
- // v1.1.4: Strict Geographic Filtering & Capitalization Sanitization
81
80
  restriction: {
82
81
  relationship: 'allow',
83
- countries: ['cd', 'fr', 'us'] // Automatically sanitized into 'CD FR US'
84
- },
85
- // v1.1.4: Native Screen-Class Targeting Controls
86
- platform: {
87
- relationship: 'deny',
88
- platforms: ['tv'] // Deny indexing out for Smart TV layouts
82
+ countries: ['cd', 'fr', 'us']
89
83
  }
90
84
  }
91
85
  ]
@@ -93,31 +87,16 @@ export async function GET() {
93
87
  {
94
88
  url: 'https://fomadev.com/news/fintech-drc-2026',
95
89
  priority: 0.85,
96
- // v1.1.8: Google News Syndication Matrix with Market Stock Tickers
97
90
  news: {
98
91
  name: 'FomaDev Insights',
99
92
  language: 'fr',
100
- publication_date: new Date(),
93
+ publication_date: new Date(), // Always dynamic to meet the strict 48h rule (v1.1.9)
101
94
  title: 'The Rise of FinTech Infrastructure in Central Africa',
102
- stock_tickers: [' NYSE:BABA ', 'NASDAQ:AAPL'] // Auto-trimmed, validated & comma-separated
95
+ stock_tickers: ['NYSE:BABA', 'NASDAQ:AAPL']
103
96
  }
104
- },
105
- {
106
- url: 'https://fomadev.com/products/tech-item',
107
- priority: 0.8,
108
- images: [
109
- {
110
- loc: 'https://fomadev.com/images/product.png',
111
- title: ' Premium Wireless Keyboard ', // v1.1.2: Auto-trimmed preventively
112
- caption: 'Close-up shot of our custom mechanical keyboard layout with XML characters like & or <', // v1.1.2: Deep XML Escaping
113
- geo_location: 'Kinshasa, Democratic Republic of the Congo', // v1.1.0 Local SEO
114
- license: 'https://fomadev.com/terms/licensing' // v1.1.0 Badging
115
- }
116
- ]
117
97
  }
118
98
  ];
119
99
 
120
- // Enable autoLastmod and sortByPriority (v1.0.8) to optimize crawl efficiency
121
100
  return getServerSitemapResponse(entries, {
122
101
  autoLastmod: true,
123
102
  sortByPriority: true
@@ -125,19 +104,49 @@ export async function GET() {
125
104
  }
126
105
  ```
127
106
 
107
+ ### 2. Generating a Master Sitemap Index (v1.2.0)
108
+
109
+ When scaling up your application, group multiple sub-sitemaps dynamically. Create a Route Handler at `app/sitemap.xml/route.ts`.
110
+
111
+ ```typescript
112
+ import { getServerSitemapIndexResponse, SitemapIndexEntry } from 'next-advanced-sitemap';
113
+
114
+ export async function GET() {
115
+ const subSitemaps: SitemapIndexEntry[] = [
116
+ {
117
+ loc: 'https://fomadev.com/sitemap-records.xml',
118
+ lastmod: new Date() // Supports native JavaScript Date polymorphism
119
+ },
120
+ {
121
+ loc: 'https://fomadev.com/sitemap-products.xml',
122
+ lastmod: '2026-07-04T12:00:00.000Z'
123
+ }
124
+ ];
125
+
126
+ // Serves a structural <sitemapindex> with optimal CDN caching headers
127
+ return getServerSitemapIndexResponse(subSitemaps, {
128
+ maxAge: 3600 // Custom cache eviction lifespan (optional)
129
+ });
130
+ }
131
+ ```
132
+
128
133
  ## API Reference
129
134
 
135
+ ### getServerSitemapIndexResponse(entries: SitemapIndexEntry[], options?: Pick<SitemapOptions, 'maxAge'>)
136
+
137
+ **Introduced in v1.2.0**. Generates a Next.js `Response` instance wrapping a structural `<sitemapindex>` tree. Ideal for routing deep content clusters while maintaining custom Edge cache distributions.
138
+
130
139
  ### getServerSitemapResponse(entries: SitemapEntry[], options?: SitemapOptions)
131
140
 
132
141
  Generates a standard Next.js `Response` object with the correct `application/xml` content-type and optimized cache headers.
133
142
 
134
- ### Options:
143
+ ### Options Matrix:
135
144
 
136
- * `autoLastmod` (boolean): If `true`, injects the current ISO date for any entry missing the `lastmod` property.
145
+ * `autoLastmod` (boolean): If `true`, injects the current ISO date for any standard entry missing the `lastmod` property.
137
146
 
138
- * `sortByPriority` (boolean): If `true`, sorts the sitemap records in a descending sequence based on their priority level (`1.0` down to `0.0`) before writing the XML stream. Items without an explicit priority fall back safely to `0.5`.
147
+ * `sortByPriority` (boolean): If `true`, sorts standard records in a descending sequence based on priority level (`1.0` down to `0.0`).
139
148
 
140
- * `maxAge` (number): (Optional) Maximum lifespan duration expressed in seconds. Transforms the HTTP communication layer payload to use a rigid `public, max-age=X, must-revalidate` schema.
149
+ * `maxAge` (number): (Optional) Maximum lifespan duration expressed in seconds. Transforms the HTTP communication layer payload to use a rigid `public, max-age=X, must-revalidate` schema. Supported by both standard and index responses.
141
150
 
142
151
  ### SitemapEntry Object
143
152
 
package/dist/index.cjs CHANGED
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ getServerSitemapIndexResponse: () => getServerSitemapIndexResponse,
23
24
  getServerSitemapResponse: () => getServerSitemapResponse
24
25
  });
25
26
  module.exports = __toCommonJS(index_exports);
@@ -422,6 +423,30 @@ function generateXml(entries, options = {}) {
422
423
  return xml;
423
424
  }
424
425
 
426
+ // src/core/builders/index-builder.ts
427
+ function buildSitemapIndexXml(entries) {
428
+ let xml = `<?xml version="1.0" encoding="UTF-8"?>
429
+ `;
430
+ xml += `<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
431
+ `;
432
+ for (const entry of entries) {
433
+ const cleanLoc = sanitizeAndValidateUrl(entry.loc, "sitemap index location");
434
+ xml += ` <sitemap>
435
+ `;
436
+ xml += ` <loc>${escapeXml(cleanLoc)}</loc>
437
+ `;
438
+ if (entry.lastmod) {
439
+ const date = entry.lastmod instanceof Date ? entry.lastmod.toISOString() : entry.lastmod;
440
+ xml += ` <lastmod>${date}</lastmod>
441
+ `;
442
+ }
443
+ xml += ` </sitemap>
444
+ `;
445
+ }
446
+ xml += `</sitemapindex>`;
447
+ return xml;
448
+ }
449
+
425
450
  // src/index.ts
426
451
  function getServerSitemapResponse(entries, options = {}) {
427
452
  const xml = generateXml(entries, options);
@@ -433,8 +458,19 @@ function getServerSitemapResponse(entries, options = {}) {
433
458
  }
434
459
  });
435
460
  }
461
+ function getServerSitemapIndexResponse(entries, options = {}) {
462
+ const xml = buildSitemapIndexXml(entries);
463
+ const cacheControlHeader = options.maxAge !== void 0 ? `public, max-age=${options.maxAge}, must-revalidate` : "public, s-maxage=86400, stale-while-revalidate";
464
+ return new Response(xml, {
465
+ headers: {
466
+ "Content-Type": "application/xml",
467
+ "Cache-Control": cacheControlHeader
468
+ }
469
+ });
470
+ }
436
471
  // Annotate the CommonJS export names for ESM import in node:
437
472
  0 && (module.exports = {
473
+ getServerSitemapIndexResponse,
438
474
  getServerSitemapResponse
439
475
  });
440
476
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/utils/xml-escape.ts","../src/core/builders/url-builder.ts","../src/core/builders/image-builder.ts","../src/core/builders/video-builder.ts","../src/core/builders/news-builder.ts","../src/core/validation/cross-validator.ts","../src/core/generator.ts"],"sourcesContent":["/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry, SitemapOptions } from './types/sitemap.js';\nimport { generateXml } from './core/generator.js';\n\nexport * from './types/sitemap.js';\n\n/**\n * Génère une réponse HTTP compatible Next.js (App Router) avec options de configuration.\n * v1.0.9 : Injection dynamique et personnalisable de l'en-tête Cache-Control via l'option maxAge\n * * @param entries - Liste des entrées du sitemap\n * @param options - Options de génération et de mise en cache (ex: autoLastmod, maxAge)\n * @returns Une instance de Response contenant le flux XML configuré\n */\nexport function getServerSitemapResponse(\n entries: SitemapEntry[], \n options: SitemapOptions = {}\n): Response {\n const xml = generateXml(entries, options);\n\n // Détermination de la stratégie de mise en cache (v1.0.9)\n const cacheControlHeader = options.maxAge !== undefined\n ? `public, max-age=${options.maxAge}, must-revalidate`\n : 'public, s-maxage=86400, stale-while-revalidate';\n\n return new Response(xml, {\n headers: {\n 'Content-Type': 'application/xml',\n 'Cache-Control': cacheControlHeader,\n },\n });\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\n/**\n * Convertit les caractères spéciaux en entités XML pour éviter la corruption du fichier.\n * Gère : <, >, &, \", '\n */\nexport function escapeXml(unsafe: string | undefined | null): string {\n if (!unsafe) return '';\n \n return unsafe.replace(/[<>&\"']/g, (c) => {\n switch (c) {\n case '<': return '&lt;';\n case '>': return '&gt;';\n case '&': return '&amp;';\n case '\"': return '&quot;';\n case \"'\": return '&apos;';\n default: return c;\n }\n });\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry, SitemapOptions } from '../../types/sitemap.js';\nimport { escapeXml } from '../../utils/xml-escape.js';\n\n/**\n * Nettoie et valide de manière stricte le format et la structure d'une URL.\n * v1.0.7 : Intégration de l'Auto-Trimming (nettoyage des espaces de début et de fin)\n */\nexport function sanitizeAndValidateUrl(rawUrl: string, context: string): string {\n const url = rawUrl ? rawUrl.trim() : '';\n\n if (!url.startsWith('http://') && !url.startsWith('https://')) {\n throw new Error(\n `[next-advanced-sitemap] Invalid URL in ${context}: \"${url}\". URLs must start with http:// or https://`\n );\n }\n\n if (url.includes(' ')) {\n throw new Error(\n `[next-advanced-sitemap] Malformed URL structure detected in ${context}: \"${url}\". Please verify spaces or special characters.`\n );\n }\n\n let isValid = false;\n if (typeof URL.canParse === 'function') {\n isValid = URL.canParse(url);\n } else {\n try {\n new URL(url);\n isValid = true;\n } catch {\n isValid = false;\n }\n }\n\n if (!isValid) {\n throw new Error(\n `[next-advanced-sitemap] Malformed URL structure detected in ${context}: \"${url}\". Please verify spaces or special characters.`\n );\n }\n\n return url;\n}\n\n/**\n * Génère le bloc XML de base pour un nœud URL (loc, alternates, lastmod, changefreq, priority).\n */\nexport function buildUrlBaseXml(entry: SitemapEntry, options: SitemapOptions, nowIso: string): string {\n let xml = '';\n \n const cleanMainUrl = sanitizeAndValidateUrl(entry.url, 'main entry');\n xml += ` <loc>${escapeXml(cleanMainUrl)}</loc>\\n`;\n\n if (entry.alternates?.length) {\n for (const alt of entry.alternates) {\n const cleanAltUrl = sanitizeAndValidateUrl(alt.href, 'alternate link');\n xml += ` <xhtml:link rel=\"alternate\" hreflang=\"${escapeXml(alt.hreflang)}\" href=\"${escapeXml(cleanAltUrl)}\" />\\n`;\n }\n }\n\n let lastmodValue = entry.lastmod;\n if (options.autoLastmod && !lastmodValue) {\n lastmodValue = nowIso;\n }\n\n if (lastmodValue) {\n const date = lastmodValue instanceof Date ? lastmodValue.toISOString() : lastmodValue;\n xml += ` <lastmod>${date}</lastmod>\\n`;\n }\n\n if (entry.changefreq) {\n xml += ` <changefreq>${entry.changefreq}</changefreq>\\n`;\n }\n\n if (entry.priority !== undefined) {\n xml += ` <priority>${(entry.priority as number).toFixed(1)}</priority>\\n`;\n }\n\n return xml;\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry } from '../../types/sitemap.js';\nimport { escapeXml } from '../../utils/xml-escape.js';\nimport { sanitizeAndValidateUrl } from './url-builder.js';\n\nexport function buildImageXml(images: SitemapEntry['images']): string {\n if (!images?.length) return '';\n\n let xml = '';\n for (const img of images) {\n const cleanImgUrl = sanitizeAndValidateUrl(img.loc, 'image location');\n \n xml += ` <image:image>\\n`;\n xml += ` <image:loc>${escapeXml(cleanImgUrl)}</image:loc>\\n`;\n \n if (img.title && img.title.trim() !== '') {\n xml += ` <image:title>${escapeXml(img.title.trim())}</image:title>\\n`;\n }\n if (img.caption && img.caption.trim() !== '') {\n xml += ` <image:caption>${escapeXml(img.caption.trim())}</image:caption>\\n`;\n }\n \n if (img.geo_location && img.geo_location.trim() !== '') {\n xml += ` <image:geo_location>${escapeXml(img.geo_location.trim())}</image:geo_location>\\n`;\n }\n \n if (img.license) {\n const cleanLicenseUrl = sanitizeAndValidateUrl(img.license, 'image license URL');\n xml += ` <image:license>${escapeXml(cleanLicenseUrl)}</image:license>\\n`;\n }\n \n xml += ` </image:image>\\n`;\n }\n \n return xml;\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry } from '../../types/sitemap.js';\nimport { escapeXml } from '../../utils/xml-escape.js';\nimport { sanitizeAndValidateUrl } from './url-builder.js';\n\nexport function buildVideoXml(videos: SitemapEntry['videos']): string {\n if (!videos?.length) return '';\n\n let xml = '';\n for (const vid of videos) {\n const cleanThumbLoc = sanitizeAndValidateUrl(vid.thumbnail_loc, 'video thumbnail');\n const cleanContentLoc = vid.content_loc ? sanitizeAndValidateUrl(vid.content_loc, 'video content location') : undefined;\n const cleanPlayerLoc = vid.player_loc ? sanitizeAndValidateUrl(vid.player_loc, 'video player location') : undefined;\n\n xml += ` <video:video>\\n`;\n xml += ` <video:thumbnail_loc>${escapeXml(cleanThumbLoc)}</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 \n if (cleanContentLoc) xml += ` <video:content_loc>${escapeXml(cleanContentLoc)}</video:content_loc>\\n`;\n if (cleanPlayerLoc) xml += ` <video:player_loc>${escapeXml(cleanPlayerLoc)}</video:player_loc>\\n`;\n \n if (vid.publication_date) {\n const vDate = vid.publication_date instanceof Date ? vid.publication_date.toISOString() : vid.publication_date;\n xml += ` <video:publication_date>${vDate}</video:publication_date>\\n`;\n }\n\n // ✨ Validation et Sérialisation de la durée (0 - 28800s)\n if (vid.duration !== undefined) {\n const finalDuration = Math.floor(vid.duration);\n if (finalDuration < 0 || finalDuration > 28800) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video duration: ${finalDuration}. Duration must be an integer between 0 and 28800 seconds (8 hours).`\n );\n }\n xml += ` <video:duration>${finalDuration}</video:duration>\\n`;\n }\n\n // ✨ Validation et Sérialisation du nombre de vues (>= 0)\n if (vid.view_count !== undefined) {\n const finalViewCount = Math.floor(vid.view_count);\n if (finalViewCount < 0) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video view_count: ${finalViewCount}. View count cannot be negative.`\n );\n }\n xml += ` <video:view_count>${finalViewCount}</video:view_count>\\n`;\n }\n\n if (vid.live) {\n xml += ` <video:live>${vid.live}</video:live>\\n`;\n }\n\n // ✨ Validation et Sérialisation des Restrictions Pays (v1.1.4)\n if (vid.restriction) {\n if (!vid.restriction.countries || vid.restriction.countries.length === 0) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video restriction: countries array cannot be empty.`\n );\n }\n\n const cleanCountries = vid.restriction.countries.map(country => {\n const code = country.trim().toUpperCase();\n if (code.length < 2 || code.length > 3) {\n throw new Error(\n `[next-advanced-sitemap] Invalid ISO country code detected: \"${country}\". Must be a valid ISO 3166 code.`\n );\n }\n return code;\n });\n\n const countriesStr = cleanCountries.join(' ');\n xml += ` <video:restriction relationship=\"${vid.restriction.relationship}\">${countriesStr}</video:restriction>\\n`;\n }\n\n // ✨ Validation et Sérialisation des Plateformes (v1.1.4)\n if (vid.platform) {\n if (!vid.platform.platforms || vid.platform.platforms.length === 0) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video platform: platforms array cannot be empty.`\n );\n }\n\n const validPlatforms = ['web', 'mobile', 'tv'];\n for (const p of vid.platform.platforms) {\n if (!validPlatforms.includes(p)) {\n throw new Error(\n `[next-advanced-sitemap] Invalid platform type: \"${p}\". Allowed values are 'web', 'mobile', or 'tv'.`\n );\n }\n }\n\n const platformsStr = vid.platform.platforms.join(' ');\n xml += ` <video:platform relationship=\"${vid.platform.relationship}\">${platformsStr}</video:platform>\\n`;\n }\n \n if (vid.requires_subscription !== undefined) {\n let subValue: 'yes' | 'no';\n\n if (typeof vid.requires_subscription === 'boolean') {\n subValue = vid.requires_subscription ? 'yes' : 'no';\n } else if (vid.requires_subscription === 'yes' || vid.requires_subscription === 'no') {\n subValue = vid.requires_subscription;\n } else {\n throw new Error(\n `[next-advanced-sitemap] Invalid value for requires_subscription: \"${vid.requires_subscription}\". Expected boolean or strict string 'yes' | 'no'.`\n );\n }\n\n xml += ` <video:requires_subscription>${subValue}</video:requires_subscription>\\n`;\n }\n\n // ✨ Validation et Sérialisation des Prix et Achats (v1.1.6)\n if (vid.price) {\n const { value, currency, type } = vid.price;\n\n if (value === undefined || value < 0) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video price value: \"${value}\". Value must be a positive number.`\n );\n }\n\n const cleanCurrency = currency ? currency.trim().toUpperCase() : '';\n if (cleanCurrency.length !== 3) {\n throw new Error(\n `[next-advanced-sitemap] Invalid ISO 4217 currency code: \"${currency}\". Currency must be exactly a 3-letter code.`\n );\n }\n\n let priceXml = ` <video:price currency=\"${cleanCurrency}\"`;\n if (type) {\n if (type !== 'rent' && type !== 'own') {\n throw new Error(\n `[next-advanced-sitemap] Invalid price type: \"${type}\". Allowed values are 'rent' or 'own'.`\n );\n }\n priceXml += ` type=\"${type}\"`;\n }\n priceXml += `>${value.toFixed(2)}</video:price>\\n`;\n \n xml += priceXml;\n }\n\n // ✨ Validation et Sérialisation de la Catégorie (v1.1.7)\n if (vid.category !== undefined) {\n const cleanCategory = vid.category.trim();\n if (!cleanCategory) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video category: category cannot be empty or just whitespaces.`\n );\n }\n if (cleanCategory.length > 256) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video category length: ${cleanCategory.length}. Maximum allowed is 256 characters.`\n );\n }\n xml += ` <video:category>${escapeXml(cleanCategory)}</video:category>\\n`;\n }\n\n // ✨ Validation et Sérialisation des Tags (v1.1.7)\n if (vid.tags) {\n if (vid.tags.length > 32) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video tags count: ${vid.tags.length}. A video can have a maximum of 32 tags.`\n );\n }\n\n for (const tag of vid.tags) {\n const cleanTag = tag.trim();\n if (!cleanTag) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video tag detected: tag cannot be empty or just whitespaces.`\n );\n }\n xml += ` <video:tag>${escapeXml(cleanTag)}</video:tag>\\n`;\n }\n }\n\n xml += ` </video:video>\\n`;\n }\n \n return xml;\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry } from '../../types/sitemap.js';\nimport { escapeXml } from '../../utils/xml-escape.js';\n\nexport function buildNewsXml(news: SitemapEntry['news']): string {\n if (!news) return '';\n\n const nDate = news.publication_date instanceof Date ? news.publication_date.toISOString() : news.publication_date;\n \n let xml = '';\n xml += ` <news:news>\\n`;\n xml += ` <news:publication>\\n`;\n xml += ` <news:name>${escapeXml(news.name)}</news:name>\\n`;\n xml += ` <news:language>${escapeXml(news.language)}</news:language>\\n`;\n xml += ` </news:publication>\\n`;\n xml += ` <news:publication_date>${nDate}</news:publication_date>\\n`;\n xml += ` <news:title>${escapeXml(news.title)}</news:title>\\n`;\n\n // ✨ Validation et Sérialisation des Stock Tickers (v1.1.8)\n if (news.stock_tickers) {\n if (!Array.isArray(news.stock_tickers)) {\n throw new Error(\n `[next-advanced-sitemap] Invalid news stock_tickers: property must be an array of strings.`\n );\n }\n\n const validatedTickers = news.stock_tickers\n .map(ticker => {\n const cleanTicker = ticker.trim();\n if (!cleanTicker) {\n throw new Error(\n `[next-advanced-sitemap] Invalid stock ticker detected: ticker element cannot be empty or just whitespaces.`\n );\n }\n if (!cleanTicker.includes(':')) {\n throw new Error(\n `[next-advanced-sitemap] Invalid stock ticker format: \"${cleanTicker}\". Expected \"EXCHANGE:TICKER\" format (e.g., \"NASDAQ:AAPL\").`\n );\n }\n return cleanTicker;\n });\n\n if (validatedTickers.length > 0) {\n // Google demande une liste séparée par des virgules (sans espaces superflus)\n const tickersString = validatedTickers.join(',');\n xml += ` <news:stock_tickers>${escapeXml(tickersString)}</news:stock_tickers>\\n`;\n }\n }\n\n xml += ` </news:news>\\n`;\n \n return xml;\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry } from '../../types/sitemap.js';\n\n/**\n * Valide la cohérence logique et sémantique croisée entre les différents champs d'une entrée.\n * Garantit un score SEO Search Console parfait.\n */\nexport function validateCrossFields(entry: SitemapEntry): void {\n // 1. Validations croisées sur l'extension Vidéo\n if (entry.videos && entry.videos.length > 0) {\n for (const vid of entry.videos) {\n \n // RÈGLE A : Conflit d'accès payant vs abonnement\n if (vid.live === 'yes' && vid.duration !== undefined && vid.duration > 0) {\n throw new Error(\n `[next-advanced-sitemap] Cross-field validation error on URL \"${entry.url}\": A live video stream cannot have a pre-defined static duration.`\n );\n }\n\n // RÈGLE B : Conflit d'abonnement requis vs Prix d'achat direct sans logique transactionnelle définie\n if (vid.requires_subscription === 'yes' && vid.price && vid.price.type === 'own') {\n throw new Error(\n `[next-advanced-sitemap] Cross-field validation error on URL \"${entry.url}\": Video cannot simultaneously require a global subscription and be available for full individual ownership (\"own\").`\n );\n }\n }\n }\n\n // 2. Validations croisées sur l'extension Google News\n if (entry.news) {\n const pubDate = entry.news.publication_date instanceof Date \n ? entry.news.publication_date \n : new Date(entry.news.publication_date);\n\n const now = new Date();\n const diffInMs = now.getTime() - pubDate.getTime();\n const diffInDays = diffInMs / (1000 * 60 * 60 * 24);\n\n // RÈGLE C : Google News n'indexe pas les articles de plus de 2 jours (48 heures) via sitemap\n if (diffInDays > 2) {\n throw new Error(\n `[next-advanced-sitemap] Cross-field validation error on URL \"${entry.url}\": Google News sitemaps only support articles published within the last 48 hours. Article date is ${diffInDays.toFixed(1)} days old.`\n );\n }\n }\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry, SitemapOptions } from '../types/sitemap.js';\nimport { buildUrlBaseXml } from './builders/url-builder.js';\nimport { buildImageXml } from './builders/image-builder.js';\nimport { buildVideoXml } from './builders/video-builder.js';\nimport { buildNewsXml } from './builders/news-builder.js';\nimport { validateCrossFields } from './validation/cross-validator.js';\n\n/**\n * Génère le flux XML complet du sitemap incluant les extensions Images, Vidéos, News et Hreflang.\n * v1.1.9 : Intégration de la validation sémantique croisée en amont.\n */\nexport function generateXml(entries: SitemapEntry[], options: SitemapOptions = {}): string {\n const now = new Date().toISOString();\n let finalEntries = [...entries];\n\n if (options.sortByPriority) {\n finalEntries.sort((a, b) => {\n const priorityA = a.priority !== undefined ? (a.priority as number) : 0.5;\n const priorityB = b.priority !== undefined ? (b.priority as number) : 0.5;\n return priorityB - priorityA;\n });\n }\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`;\n xml += ` xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\"\\n`;\n xml += ` xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\"\\n`;\n xml += ` xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\"\\n`;\n xml += ` xmlns:xhtml=\"http://www.w3.org/1999/xhtml\">\\n`;\n\n for (const entry of finalEntries) {\n // 🔥 ÉTAPE CRUCIALE v1.1.9 : Validation croisée pré-génération\n validateCrossFields(entry);\n\n xml += ` <url>\\n`;\n \n // 1. Éléments de base et hreflang alternatifs\n xml += buildUrlBaseXml(entry, options, now);\n\n // 2. Extension Images Google\n xml += buildImageXml(entry.images);\n\n // 3. Extension Vidéos Google (Validations v1.1.3 & v1.1.4 intégrées)\n xml += buildVideoXml(entry.videos);\n\n // 4. Extension News Google\n xml += buildNewsXml(entry.news);\n\n xml += ` </url>\\n`;\n }\n\n xml += `</urlset>`;\n return xml;\n}"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/utils/xml-escape.ts","../src/core/builders/url-builder.ts","../src/core/builders/image-builder.ts","../src/core/builders/video-builder.ts","../src/core/builders/news-builder.ts","../src/core/validation/cross-validator.ts","../src/core/generator.ts","../src/core/builders/index-builder.ts"],"sourcesContent":["/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry, SitemapOptions, SitemapIndexEntry } from './types/sitemap.js';\nimport { generateXml } from './core/generator.js';\nimport { buildSitemapIndexXml } from './core/builders/index-builder.js';\n\nexport * from './types/sitemap.js';\n\n/**\n * Génère une réponse HTTP compatible Next.js (App Router) avec options de configuration.\n * v1.0.9 : Injection dynamique et personnalisable de l'en-tête Cache-Control via l'option maxAge.\n * \n * @param entries - Liste des entrées du sitemap\n * @param options - Options de génération et de mise en cache (ex: autoLastmod, maxAge)\n * @returns Une instance de Response contenant le flux XML configuré\n */\nexport function getServerSitemapResponse(\n entries: SitemapEntry[], \n options: SitemapOptions = {}\n): Response {\n const xml = generateXml(entries, options);\n\n // Détermination de la stratégie de mise en cache (v1.0.9)\n const cacheControlHeader = options.maxAge !== undefined\n ? `public, max-age=${options.maxAge}, must-revalidate`\n : 'public, s-maxage=86400, stale-while-revalidate';\n\n return new Response(xml, {\n headers: {\n 'Content-Type': 'application/xml',\n 'Cache-Control': cacheControlHeader,\n },\n });\n}\n\n/**\n * ✨ v1.2.0 : Génère une réponse HTTP Next.js (App Router) pour un Index de Sitemaps.\n * Permet de lister et de regrouper plusieurs sous-fichiers XML sitemaps.\n * \n * @param entries - Liste des sous-sitemaps composant l'index\n * @param options - Options de configuration (ex: maxAge pour le cache)\n * @returns Une instance de Response contenant le flux XML de l'index\n */\nexport function getServerSitemapIndexResponse(\n entries: SitemapIndexEntry[],\n options: Pick<SitemapOptions, 'maxAge'> = {}\n): Response {\n const xml = buildSitemapIndexXml(entries);\n\n // Détermination de la stratégie de mise en cache (v1.2.0)\n const cacheControlHeader = options.maxAge !== undefined\n ? `public, max-age=${options.maxAge}, must-revalidate`\n : 'public, s-maxage=86400, stale-while-revalidate';\n\n return new Response(xml, {\n headers: {\n 'Content-Type': 'application/xml',\n 'Cache-Control': cacheControlHeader,\n },\n });\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\n/**\n * Convertit les caractères spéciaux en entités XML pour éviter la corruption du fichier.\n * Gère : <, >, &, \", '\n */\nexport function escapeXml(unsafe: string | undefined | null): string {\n if (!unsafe) return '';\n \n return unsafe.replace(/[<>&\"']/g, (c) => {\n switch (c) {\n case '<': return '&lt;';\n case '>': return '&gt;';\n case '&': return '&amp;';\n case '\"': return '&quot;';\n case \"'\": return '&apos;';\n default: return c;\n }\n });\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry, SitemapOptions } from '../../types/sitemap.js';\nimport { escapeXml } from '../../utils/xml-escape.js';\n\n/**\n * Nettoie et valide de manière stricte le format et la structure d'une URL.\n * v1.0.7 : Intégration de l'Auto-Trimming (nettoyage des espaces de début et de fin)\n */\nexport function sanitizeAndValidateUrl(rawUrl: string, context: string): string {\n const url = rawUrl ? rawUrl.trim() : '';\n\n if (!url.startsWith('http://') && !url.startsWith('https://')) {\n throw new Error(\n `[next-advanced-sitemap] Invalid URL in ${context}: \"${url}\". URLs must start with http:// or https://`\n );\n }\n\n if (url.includes(' ')) {\n throw new Error(\n `[next-advanced-sitemap] Malformed URL structure detected in ${context}: \"${url}\". Please verify spaces or special characters.`\n );\n }\n\n let isValid = false;\n if (typeof URL.canParse === 'function') {\n isValid = URL.canParse(url);\n } else {\n try {\n new URL(url);\n isValid = true;\n } catch {\n isValid = false;\n }\n }\n\n if (!isValid) {\n throw new Error(\n `[next-advanced-sitemap] Malformed URL structure detected in ${context}: \"${url}\". Please verify spaces or special characters.`\n );\n }\n\n return url;\n}\n\n/**\n * Génère le bloc XML de base pour un nœud URL (loc, alternates, lastmod, changefreq, priority).\n */\nexport function buildUrlBaseXml(entry: SitemapEntry, options: SitemapOptions, nowIso: string): string {\n let xml = '';\n \n const cleanMainUrl = sanitizeAndValidateUrl(entry.url, 'main entry');\n xml += ` <loc>${escapeXml(cleanMainUrl)}</loc>\\n`;\n\n if (entry.alternates?.length) {\n for (const alt of entry.alternates) {\n const cleanAltUrl = sanitizeAndValidateUrl(alt.href, 'alternate link');\n xml += ` <xhtml:link rel=\"alternate\" hreflang=\"${escapeXml(alt.hreflang)}\" href=\"${escapeXml(cleanAltUrl)}\" />\\n`;\n }\n }\n\n let lastmodValue = entry.lastmod;\n if (options.autoLastmod && !lastmodValue) {\n lastmodValue = nowIso;\n }\n\n if (lastmodValue) {\n const date = lastmodValue instanceof Date ? lastmodValue.toISOString() : lastmodValue;\n xml += ` <lastmod>${date}</lastmod>\\n`;\n }\n\n if (entry.changefreq) {\n xml += ` <changefreq>${entry.changefreq}</changefreq>\\n`;\n }\n\n if (entry.priority !== undefined) {\n xml += ` <priority>${(entry.priority as number).toFixed(1)}</priority>\\n`;\n }\n\n return xml;\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry } from '../../types/sitemap.js';\nimport { escapeXml } from '../../utils/xml-escape.js';\nimport { sanitizeAndValidateUrl } from './url-builder.js';\n\nexport function buildImageXml(images: SitemapEntry['images']): string {\n if (!images?.length) return '';\n\n let xml = '';\n for (const img of images) {\n const cleanImgUrl = sanitizeAndValidateUrl(img.loc, 'image location');\n \n xml += ` <image:image>\\n`;\n xml += ` <image:loc>${escapeXml(cleanImgUrl)}</image:loc>\\n`;\n \n if (img.title && img.title.trim() !== '') {\n xml += ` <image:title>${escapeXml(img.title.trim())}</image:title>\\n`;\n }\n if (img.caption && img.caption.trim() !== '') {\n xml += ` <image:caption>${escapeXml(img.caption.trim())}</image:caption>\\n`;\n }\n \n if (img.geo_location && img.geo_location.trim() !== '') {\n xml += ` <image:geo_location>${escapeXml(img.geo_location.trim())}</image:geo_location>\\n`;\n }\n \n if (img.license) {\n const cleanLicenseUrl = sanitizeAndValidateUrl(img.license, 'image license URL');\n xml += ` <image:license>${escapeXml(cleanLicenseUrl)}</image:license>\\n`;\n }\n \n xml += ` </image:image>\\n`;\n }\n \n return xml;\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry } from '../../types/sitemap.js';\nimport { escapeXml } from '../../utils/xml-escape.js';\nimport { sanitizeAndValidateUrl } from './url-builder.js';\n\nexport function buildVideoXml(videos: SitemapEntry['videos']): string {\n if (!videos?.length) return '';\n\n let xml = '';\n for (const vid of videos) {\n const cleanThumbLoc = sanitizeAndValidateUrl(vid.thumbnail_loc, 'video thumbnail');\n const cleanContentLoc = vid.content_loc ? sanitizeAndValidateUrl(vid.content_loc, 'video content location') : undefined;\n const cleanPlayerLoc = vid.player_loc ? sanitizeAndValidateUrl(vid.player_loc, 'video player location') : undefined;\n\n xml += ` <video:video>\\n`;\n xml += ` <video:thumbnail_loc>${escapeXml(cleanThumbLoc)}</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 \n if (cleanContentLoc) xml += ` <video:content_loc>${escapeXml(cleanContentLoc)}</video:content_loc>\\n`;\n if (cleanPlayerLoc) xml += ` <video:player_loc>${escapeXml(cleanPlayerLoc)}</video:player_loc>\\n`;\n \n if (vid.publication_date) {\n const vDate = vid.publication_date instanceof Date ? vid.publication_date.toISOString() : vid.publication_date;\n xml += ` <video:publication_date>${vDate}</video:publication_date>\\n`;\n }\n\n // ✨ Validation et Sérialisation de la durée (0 - 28800s)\n if (vid.duration !== undefined) {\n const finalDuration = Math.floor(vid.duration);\n if (finalDuration < 0 || finalDuration > 28800) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video duration: ${finalDuration}. Duration must be an integer between 0 and 28800 seconds (8 hours).`\n );\n }\n xml += ` <video:duration>${finalDuration}</video:duration>\\n`;\n }\n\n // ✨ Validation et Sérialisation du nombre de vues (>= 0)\n if (vid.view_count !== undefined) {\n const finalViewCount = Math.floor(vid.view_count);\n if (finalViewCount < 0) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video view_count: ${finalViewCount}. View count cannot be negative.`\n );\n }\n xml += ` <video:view_count>${finalViewCount}</video:view_count>\\n`;\n }\n\n if (vid.live) {\n xml += ` <video:live>${vid.live}</video:live>\\n`;\n }\n\n // ✨ Validation et Sérialisation des Restrictions Pays (v1.1.4)\n if (vid.restriction) {\n if (!vid.restriction.countries || vid.restriction.countries.length === 0) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video restriction: countries array cannot be empty.`\n );\n }\n\n const cleanCountries = vid.restriction.countries.map(country => {\n const code = country.trim().toUpperCase();\n if (code.length < 2 || code.length > 3) {\n throw new Error(\n `[next-advanced-sitemap] Invalid ISO country code detected: \"${country}\". Must be a valid ISO 3166 code.`\n );\n }\n return code;\n });\n\n const countriesStr = cleanCountries.join(' ');\n xml += ` <video:restriction relationship=\"${vid.restriction.relationship}\">${countriesStr}</video:restriction>\\n`;\n }\n\n // ✨ Validation et Sérialisation des Plateformes (v1.1.4)\n if (vid.platform) {\n if (!vid.platform.platforms || vid.platform.platforms.length === 0) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video platform: platforms array cannot be empty.`\n );\n }\n\n const validPlatforms = ['web', 'mobile', 'tv'];\n for (const p of vid.platform.platforms) {\n if (!validPlatforms.includes(p)) {\n throw new Error(\n `[next-advanced-sitemap] Invalid platform type: \"${p}\". Allowed values are 'web', 'mobile', or 'tv'.`\n );\n }\n }\n\n const platformsStr = vid.platform.platforms.join(' ');\n xml += ` <video:platform relationship=\"${vid.platform.relationship}\">${platformsStr}</video:platform>\\n`;\n }\n \n if (vid.requires_subscription !== undefined) {\n let subValue: 'yes' | 'no';\n\n if (typeof vid.requires_subscription === 'boolean') {\n subValue = vid.requires_subscription ? 'yes' : 'no';\n } else if (vid.requires_subscription === 'yes' || vid.requires_subscription === 'no') {\n subValue = vid.requires_subscription;\n } else {\n throw new Error(\n `[next-advanced-sitemap] Invalid value for requires_subscription: \"${vid.requires_subscription}\". Expected boolean or strict string 'yes' | 'no'.`\n );\n }\n\n xml += ` <video:requires_subscription>${subValue}</video:requires_subscription>\\n`;\n }\n\n // ✨ Validation et Sérialisation des Prix et Achats (v1.1.6)\n if (vid.price) {\n const { value, currency, type } = vid.price;\n\n if (value === undefined || value < 0) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video price value: \"${value}\". Value must be a positive number.`\n );\n }\n\n const cleanCurrency = currency ? currency.trim().toUpperCase() : '';\n if (cleanCurrency.length !== 3) {\n throw new Error(\n `[next-advanced-sitemap] Invalid ISO 4217 currency code: \"${currency}\". Currency must be exactly a 3-letter code.`\n );\n }\n\n let priceXml = ` <video:price currency=\"${cleanCurrency}\"`;\n if (type) {\n if (type !== 'rent' && type !== 'own') {\n throw new Error(\n `[next-advanced-sitemap] Invalid price type: \"${type}\". Allowed values are 'rent' or 'own'.`\n );\n }\n priceXml += ` type=\"${type}\"`;\n }\n priceXml += `>${value.toFixed(2)}</video:price>\\n`;\n \n xml += priceXml;\n }\n\n // ✨ Validation et Sérialisation de la Catégorie (v1.1.7)\n if (vid.category !== undefined) {\n const cleanCategory = vid.category.trim();\n if (!cleanCategory) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video category: category cannot be empty or just whitespaces.`\n );\n }\n if (cleanCategory.length > 256) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video category length: ${cleanCategory.length}. Maximum allowed is 256 characters.`\n );\n }\n xml += ` <video:category>${escapeXml(cleanCategory)}</video:category>\\n`;\n }\n\n // ✨ Validation et Sérialisation des Tags (v1.1.7)\n if (vid.tags) {\n if (vid.tags.length > 32) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video tags count: ${vid.tags.length}. A video can have a maximum of 32 tags.`\n );\n }\n\n for (const tag of vid.tags) {\n const cleanTag = tag.trim();\n if (!cleanTag) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video tag detected: tag cannot be empty or just whitespaces.`\n );\n }\n xml += ` <video:tag>${escapeXml(cleanTag)}</video:tag>\\n`;\n }\n }\n\n xml += ` </video:video>\\n`;\n }\n \n return xml;\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry } from '../../types/sitemap.js';\nimport { escapeXml } from '../../utils/xml-escape.js';\n\nexport function buildNewsXml(news: SitemapEntry['news']): string {\n if (!news) return '';\n\n const nDate = news.publication_date instanceof Date ? news.publication_date.toISOString() : news.publication_date;\n \n let xml = '';\n xml += ` <news:news>\\n`;\n xml += ` <news:publication>\\n`;\n xml += ` <news:name>${escapeXml(news.name)}</news:name>\\n`;\n xml += ` <news:language>${escapeXml(news.language)}</news:language>\\n`;\n xml += ` </news:publication>\\n`;\n xml += ` <news:publication_date>${nDate}</news:publication_date>\\n`;\n xml += ` <news:title>${escapeXml(news.title)}</news:title>\\n`;\n\n // ✨ Validation et Sérialisation des Stock Tickers (v1.1.8)\n if (news.stock_tickers) {\n if (!Array.isArray(news.stock_tickers)) {\n throw new Error(\n `[next-advanced-sitemap] Invalid news stock_tickers: property must be an array of strings.`\n );\n }\n\n const validatedTickers = news.stock_tickers\n .map(ticker => {\n const cleanTicker = ticker.trim();\n if (!cleanTicker) {\n throw new Error(\n `[next-advanced-sitemap] Invalid stock ticker detected: ticker element cannot be empty or just whitespaces.`\n );\n }\n if (!cleanTicker.includes(':')) {\n throw new Error(\n `[next-advanced-sitemap] Invalid stock ticker format: \"${cleanTicker}\". Expected \"EXCHANGE:TICKER\" format (e.g., \"NASDAQ:AAPL\").`\n );\n }\n return cleanTicker;\n });\n\n if (validatedTickers.length > 0) {\n // Google demande une liste séparée par des virgules (sans espaces superflus)\n const tickersString = validatedTickers.join(',');\n xml += ` <news:stock_tickers>${escapeXml(tickersString)}</news:stock_tickers>\\n`;\n }\n }\n\n xml += ` </news:news>\\n`;\n \n return xml;\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry } from '../../types/sitemap.js';\n\n/**\n * Valide la cohérence logique et sémantique croisée entre les différents champs d'une entrée.\n * Garantit un score SEO Search Console parfait.\n */\nexport function validateCrossFields(entry: SitemapEntry): void {\n // 1. Validations croisées sur l'extension Vidéo\n if (entry.videos && entry.videos.length > 0) {\n for (const vid of entry.videos) {\n \n // RÈGLE A : Conflit d'accès payant vs abonnement\n if (vid.live === 'yes' && vid.duration !== undefined && vid.duration > 0) {\n throw new Error(\n `[next-advanced-sitemap] Cross-field validation error on URL \"${entry.url}\": A live video stream cannot have a pre-defined static duration.`\n );\n }\n\n // RÈGLE B : Conflit d'abonnement requis vs Prix d'achat direct sans logique transactionnelle définie\n if (vid.requires_subscription === 'yes' && vid.price && vid.price.type === 'own') {\n throw new Error(\n `[next-advanced-sitemap] Cross-field validation error on URL \"${entry.url}\": Video cannot simultaneously require a global subscription and be available for full individual ownership (\"own\").`\n );\n }\n }\n }\n\n // 2. Validations croisées sur l'extension Google News\n if (entry.news) {\n const pubDate = entry.news.publication_date instanceof Date \n ? entry.news.publication_date \n : new Date(entry.news.publication_date);\n\n const now = new Date();\n const diffInMs = now.getTime() - pubDate.getTime();\n const diffInDays = diffInMs / (1000 * 60 * 60 * 24);\n\n // RÈGLE C : Google News n'indexe pas les articles de plus de 2 jours (48 heures) via sitemap\n if (diffInDays > 2) {\n throw new Error(\n `[next-advanced-sitemap] Cross-field validation error on URL \"${entry.url}\": Google News sitemaps only support articles published within the last 48 hours. Article date is ${diffInDays.toFixed(1)} days old.`\n );\n }\n }\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry, SitemapOptions } from '../types/sitemap.js';\nimport { buildUrlBaseXml } from './builders/url-builder.js';\nimport { buildImageXml } from './builders/image-builder.js';\nimport { buildVideoXml } from './builders/video-builder.js';\nimport { buildNewsXml } from './builders/news-builder.js';\nimport { validateCrossFields } from './validation/cross-validator.js';\n\n/**\n * Génère le flux XML complet du sitemap incluant les extensions Images, Vidéos, News et Hreflang.\n * v1.1.9 : Intégration de la validation sémantique croisée en amont.\n */\nexport function generateXml(entries: SitemapEntry[], options: SitemapOptions = {}): string {\n const now = new Date().toISOString();\n let finalEntries = [...entries];\n\n if (options.sortByPriority) {\n finalEntries.sort((a, b) => {\n const priorityA = a.priority !== undefined ? (a.priority as number) : 0.5;\n const priorityB = b.priority !== undefined ? (b.priority as number) : 0.5;\n return priorityB - priorityA;\n });\n }\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`;\n xml += ` xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\"\\n`;\n xml += ` xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\"\\n`;\n xml += ` xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\"\\n`;\n xml += ` xmlns:xhtml=\"http://www.w3.org/1999/xhtml\">\\n`;\n\n for (const entry of finalEntries) {\n // 🔥 ÉTAPE CRUCIALE v1.1.9 : Validation croisée pré-génération\n validateCrossFields(entry);\n\n xml += ` <url>\\n`;\n \n // 1. Éléments de base et hreflang alternatifs\n xml += buildUrlBaseXml(entry, options, now);\n\n // 2. Extension Images Google\n xml += buildImageXml(entry.images);\n\n // 3. Extension Vidéos Google (Validations v1.1.3 & v1.1.4 intégrées)\n xml += buildVideoXml(entry.videos);\n\n // 4. Extension News Google\n xml += buildNewsXml(entry.news);\n\n xml += ` </url>\\n`;\n }\n\n xml += `</urlset>`;\n return xml;\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapIndexEntry } from '../../types/sitemap.js';\nimport { escapeXml } from '../../utils/xml-escape.js';\nimport { sanitizeAndValidateUrl } from './url-builder.js';\n\n/**\n * Génère la structure brute XML pour un fichier d'indexation de sitemaps.\n */\nexport function buildSitemapIndexXml(entries: SitemapIndexEntry[]): 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 entry of entries) {\n const cleanLoc = sanitizeAndValidateUrl(entry.loc, 'sitemap index location');\n \n xml += ` <sitemap>\\n`;\n xml += ` <loc>${escapeXml(cleanLoc)}</loc>\\n`;\n \n if (entry.lastmod) {\n const date = entry.lastmod instanceof Date ? entry.lastmod.toISOString() : entry.lastmod;\n xml += ` <lastmod>${date}</lastmod>\\n`;\n }\n \n xml += ` </sitemap>\\n`;\n }\n\n xml += `</sitemapindex>`;\n return xml;\n}"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACSO,SAAS,UAAU,QAA2C;AACnE,MAAI,CAAC,OAAQ,QAAO;AAEpB,SAAO,OAAO,QAAQ,YAAY,CAAC,MAAM;AACvC,YAAQ,GAAG;AAAA,MACT,KAAK;AAAK,eAAO;AAAA,MACjB,KAAK;AAAK,eAAO;AAAA,MACjB,KAAK;AAAK,eAAO;AAAA,MACjB,KAAK;AAAK,eAAO;AAAA,MACjB,KAAK;AAAK,eAAO;AAAA,MACjB;AAAS,eAAO;AAAA,IAClB;AAAA,EACF,CAAC;AACH;;;ACVO,SAAS,uBAAuB,QAAgB,SAAyB;AAC9E,QAAM,MAAM,SAAS,OAAO,KAAK,IAAI;AAErC,MAAI,CAAC,IAAI,WAAW,SAAS,KAAK,CAAC,IAAI,WAAW,UAAU,GAAG;AAC7D,UAAM,IAAI;AAAA,MACR,0CAA0C,OAAO,MAAM,GAAG;AAAA,IAC5D;AAAA,EACF;AAEA,MAAI,IAAI,SAAS,GAAG,GAAG;AACrB,UAAM,IAAI;AAAA,MACR,+DAA+D,OAAO,MAAM,GAAG;AAAA,IACjF;AAAA,EACF;AAEA,MAAI,UAAU;AACd,MAAI,OAAO,IAAI,aAAa,YAAY;AACtC,cAAU,IAAI,SAAS,GAAG;AAAA,EAC5B,OAAO;AACL,QAAI;AACF,UAAI,IAAI,GAAG;AACX,gBAAU;AAAA,IACZ,QAAQ;AACN,gBAAU;AAAA,IACZ;AAAA,EACF;AAEA,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR,+DAA+D,OAAO,MAAM,GAAG;AAAA,IACjF;AAAA,EACF;AAEA,SAAO;AACT;AAKO,SAAS,gBAAgB,OAAqB,SAAyB,QAAwB;AACpG,MAAI,MAAM;AAEV,QAAM,eAAe,uBAAuB,MAAM,KAAK,YAAY;AACnE,SAAO,YAAY,UAAU,YAAY,CAAC;AAAA;AAE1C,MAAI,MAAM,YAAY,QAAQ;AAC5B,eAAW,OAAO,MAAM,YAAY;AAClC,YAAM,cAAc,uBAAuB,IAAI,MAAM,gBAAgB;AACrE,aAAO,6CAA6C,UAAU,IAAI,QAAQ,CAAC,WAAW,UAAU,WAAW,CAAC;AAAA;AAAA,IAC9G;AAAA,EACF;AAEA,MAAI,eAAe,MAAM;AACzB,MAAI,QAAQ,eAAe,CAAC,cAAc;AACxC,mBAAe;AAAA,EACjB;AAEA,MAAI,cAAc;AAChB,UAAM,OAAO,wBAAwB,OAAO,aAAa,YAAY,IAAI;AACzE,WAAO,gBAAgB,IAAI;AAAA;AAAA,EAC7B;AAEA,MAAI,MAAM,YAAY;AACpB,WAAO,mBAAmB,MAAM,UAAU;AAAA;AAAA,EAC5C;AAEA,MAAI,MAAM,aAAa,QAAW;AAChC,WAAO,iBAAkB,MAAM,SAAoB,QAAQ,CAAC,CAAC;AAAA;AAAA,EAC/D;AAEA,SAAO;AACT;;;AC1EO,SAAS,cAAc,QAAwC;AACpE,MAAI,CAAC,QAAQ,OAAQ,QAAO;AAE5B,MAAI,MAAM;AACV,aAAW,OAAO,QAAQ;AACxB,UAAM,cAAc,uBAAuB,IAAI,KAAK,gBAAgB;AAEpE,WAAO;AAAA;AACP,WAAO,oBAAoB,UAAU,WAAW,CAAC;AAAA;AAEjD,QAAI,IAAI,SAAS,IAAI,MAAM,KAAK,MAAM,IAAI;AACxC,aAAO,sBAAsB,UAAU,IAAI,MAAM,KAAK,CAAC,CAAC;AAAA;AAAA,IAC1D;AACA,QAAI,IAAI,WAAW,IAAI,QAAQ,KAAK,MAAM,IAAI;AAC5C,aAAO,wBAAwB,UAAU,IAAI,QAAQ,KAAK,CAAC,CAAC;AAAA;AAAA,IAC9D;AAEA,QAAI,IAAI,gBAAgB,IAAI,aAAa,KAAK,MAAM,IAAI;AACtD,aAAO,6BAA6B,UAAU,IAAI,aAAa,KAAK,CAAC,CAAC;AAAA;AAAA,IACxE;AAEA,QAAI,IAAI,SAAS;AACf,YAAM,kBAAkB,uBAAuB,IAAI,SAAS,mBAAmB;AAC/E,aAAO,wBAAwB,UAAU,eAAe,CAAC;AAAA;AAAA,IAC3D;AAEA,WAAO;AAAA;AAAA,EACT;AAEA,SAAO;AACT;;;AC9BO,SAAS,cAAc,QAAwC;AACpE,MAAI,CAAC,QAAQ,OAAQ,QAAO;AAE5B,MAAI,MAAM;AACV,aAAW,OAAO,QAAQ;AACxB,UAAM,gBAAgB,uBAAuB,IAAI,eAAe,iBAAiB;AACjF,UAAM,kBAAkB,IAAI,cAAc,uBAAuB,IAAI,aAAa,wBAAwB,IAAI;AAC9G,UAAM,iBAAiB,IAAI,aAAa,uBAAuB,IAAI,YAAY,uBAAuB,IAAI;AAE1G,WAAO;AAAA;AACP,WAAO,8BAA8B,UAAU,aAAa,CAAC;AAAA;AAC7D,WAAO,sBAAsB,UAAU,IAAI,KAAK,CAAC;AAAA;AACjD,WAAO,4BAA4B,UAAU,IAAI,WAAW,CAAC;AAAA;AAE7D,QAAI,gBAAiB,QAAO,4BAA4B,UAAU,eAAe,CAAC;AAAA;AAClF,QAAI,eAAgB,QAAO,2BAA2B,UAAU,cAAc,CAAC;AAAA;AAE/E,QAAI,IAAI,kBAAkB;AACxB,YAAM,QAAQ,IAAI,4BAA4B,OAAO,IAAI,iBAAiB,YAAY,IAAI,IAAI;AAC9F,aAAO,iCAAiC,KAAK;AAAA;AAAA,IAC/C;AAGA,QAAI,IAAI,aAAa,QAAW;AAC9B,YAAM,gBAAgB,KAAK,MAAM,IAAI,QAAQ;AAC7C,UAAI,gBAAgB,KAAK,gBAAgB,OAAO;AAC9C,cAAM,IAAI;AAAA,UACR,mDAAmD,aAAa;AAAA,QAClE;AAAA,MACF;AACA,aAAO,yBAAyB,aAAa;AAAA;AAAA,IAC/C;AAGA,QAAI,IAAI,eAAe,QAAW;AAChC,YAAM,iBAAiB,KAAK,MAAM,IAAI,UAAU;AAChD,UAAI,iBAAiB,GAAG;AACtB,cAAM,IAAI;AAAA,UACR,qDAAqD,cAAc;AAAA,QACrE;AAAA,MACF;AACA,aAAO,2BAA2B,cAAc;AAAA;AAAA,IAClD;AAEA,QAAI,IAAI,MAAM;AACZ,aAAO,qBAAqB,IAAI,IAAI;AAAA;AAAA,IACtC;AAGA,QAAI,IAAI,aAAa;AACnB,UAAI,CAAC,IAAI,YAAY,aAAa,IAAI,YAAY,UAAU,WAAW,GAAG;AACxE,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAEA,YAAM,iBAAiB,IAAI,YAAY,UAAU,IAAI,aAAW;AAC9D,cAAM,OAAO,QAAQ,KAAK,EAAE,YAAY;AACxC,YAAI,KAAK,SAAS,KAAK,KAAK,SAAS,GAAG;AACtC,gBAAM,IAAI;AAAA,YACR,+DAA+D,OAAO;AAAA,UACxE;AAAA,QACF;AACA,eAAO;AAAA,MACT,CAAC;AAED,YAAM,eAAe,eAAe,KAAK,GAAG;AAC5C,aAAO,0CAA0C,IAAI,YAAY,YAAY,KAAK,YAAY;AAAA;AAAA,IAChG;AAGA,QAAI,IAAI,UAAU;AAChB,UAAI,CAAC,IAAI,SAAS,aAAa,IAAI,SAAS,UAAU,WAAW,GAAG;AAClE,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAEA,YAAM,iBAAiB,CAAC,OAAO,UAAU,IAAI;AAC7C,iBAAW,KAAK,IAAI,SAAS,WAAW;AACtC,YAAI,CAAC,eAAe,SAAS,CAAC,GAAG;AAC/B,gBAAM,IAAI;AAAA,YACR,mDAAmD,CAAC;AAAA,UACtD;AAAA,QACF;AAAA,MACF;AAEA,YAAM,eAAe,IAAI,SAAS,UAAU,KAAK,GAAG;AACpD,aAAO,uCAAuC,IAAI,SAAS,YAAY,KAAK,YAAY;AAAA;AAAA,IAC1F;AAEA,QAAI,IAAI,0BAA0B,QAAW;AAC3C,UAAI;AAEJ,UAAI,OAAO,IAAI,0BAA0B,WAAW;AAClD,mBAAW,IAAI,wBAAwB,QAAQ;AAAA,MACjD,WAAW,IAAI,0BAA0B,SAAS,IAAI,0BAA0B,MAAM;AACpF,mBAAW,IAAI;AAAA,MACjB,OAAO;AACL,cAAM,IAAI;AAAA,UACR,qEAAqE,IAAI,qBAAqB;AAAA,QAChG;AAAA,MACF;AAEA,aAAO,sCAAsC,QAAQ;AAAA;AAAA,IACvD;AAGA,QAAI,IAAI,OAAO;AACb,YAAM,EAAE,OAAO,UAAU,KAAK,IAAI,IAAI;AAEtC,UAAI,UAAU,UAAa,QAAQ,GAAG;AACpC,cAAM,IAAI;AAAA,UACR,uDAAuD,KAAK;AAAA,QAC9D;AAAA,MACF;AAEA,YAAM,gBAAgB,WAAW,SAAS,KAAK,EAAE,YAAY,IAAI;AACjE,UAAI,cAAc,WAAW,GAAG;AAC9B,cAAM,IAAI;AAAA,UACR,4DAA4D,QAAQ;AAAA,QACtE;AAAA,MACF;AAEA,UAAI,WAAW,gCAAgC,aAAa;AAC5D,UAAI,MAAM;AACR,YAAI,SAAS,UAAU,SAAS,OAAO;AACrC,gBAAM,IAAI;AAAA,YACR,gDAAgD,IAAI;AAAA,UACtD;AAAA,QACF;AACA,oBAAY,UAAU,IAAI;AAAA,MAC5B;AACA,kBAAY,IAAI,MAAM,QAAQ,CAAC,CAAC;AAAA;AAEhC,aAAO;AAAA,IACT;AAGA,QAAI,IAAI,aAAa,QAAW;AAC9B,YAAM,gBAAgB,IAAI,SAAS,KAAK;AACxC,UAAI,CAAC,eAAe;AAClB,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AACA,UAAI,cAAc,SAAS,KAAK;AAC9B,cAAM,IAAI;AAAA,UACR,0DAA0D,cAAc,MAAM;AAAA,QAChF;AAAA,MACF;AACA,aAAO,yBAAyB,UAAU,aAAa,CAAC;AAAA;AAAA,IAC1D;AAGA,QAAI,IAAI,MAAM;AACZ,UAAI,IAAI,KAAK,SAAS,IAAI;AACxB,cAAM,IAAI;AAAA,UACR,qDAAqD,IAAI,KAAK,MAAM;AAAA,QACtE;AAAA,MACF;AAEA,iBAAW,OAAO,IAAI,MAAM;AAC1B,cAAM,WAAW,IAAI,KAAK;AAC1B,YAAI,CAAC,UAAU;AACb,gBAAM,IAAI;AAAA,YACR;AAAA,UACF;AAAA,QACF;AACA,eAAO,oBAAoB,UAAU,QAAQ,CAAC;AAAA;AAAA,MAChD;AAAA,IACF;AAEA,WAAO;AAAA;AAAA,EACT;AAEA,SAAO;AACT;;;AClLO,SAAS,aAAa,MAAoC;AAC/D,MAAI,CAAC,KAAM,QAAO;AAElB,QAAM,QAAQ,KAAK,4BAA4B,OAAO,KAAK,iBAAiB,YAAY,IAAI,KAAK;AAEjG,MAAI,MAAM;AACV,SAAO;AAAA;AACP,SAAO;AAAA;AACP,SAAO,sBAAsB,UAAU,KAAK,IAAI,CAAC;AAAA;AACjD,SAAO,0BAA0B,UAAU,KAAK,QAAQ,CAAC;AAAA;AACzD,SAAO;AAAA;AACP,SAAO,gCAAgC,KAAK;AAAA;AAC5C,SAAO,qBAAqB,UAAU,KAAK,KAAK,CAAC;AAAA;AAGjD,MAAI,KAAK,eAAe;AACtB,QAAI,CAAC,MAAM,QAAQ,KAAK,aAAa,GAAG;AACtC,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,UAAM,mBAAmB,KAAK,cAC3B,IAAI,YAAU;AACb,YAAM,cAAc,OAAO,KAAK;AAChC,UAAI,CAAC,aAAa;AAChB,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AACA,UAAI,CAAC,YAAY,SAAS,GAAG,GAAG;AAC9B,cAAM,IAAI;AAAA,UACR,yDAAyD,WAAW;AAAA,QACtE;AAAA,MACF;AACA,aAAO;AAAA,IACT,CAAC;AAEH,QAAI,iBAAiB,SAAS,GAAG;AAE/B,YAAM,gBAAgB,iBAAiB,KAAK,GAAG;AAC/C,aAAO,6BAA6B,UAAU,aAAa,CAAC;AAAA;AAAA,IAC9D;AAAA,EACF;AAEA,SAAO;AAAA;AAEP,SAAO;AACT;;;AC7CO,SAAS,oBAAoB,OAA2B;AAE7D,MAAI,MAAM,UAAU,MAAM,OAAO,SAAS,GAAG;AAC3C,eAAW,OAAO,MAAM,QAAQ;AAG9B,UAAI,IAAI,SAAS,SAAS,IAAI,aAAa,UAAa,IAAI,WAAW,GAAG;AACxE,cAAM,IAAI;AAAA,UACR,gEAAgE,MAAM,GAAG;AAAA,QAC3E;AAAA,MACF;AAGA,UAAI,IAAI,0BAA0B,SAAS,IAAI,SAAS,IAAI,MAAM,SAAS,OAAO;AAChF,cAAM,IAAI;AAAA,UACR,gEAAgE,MAAM,GAAG;AAAA,QAC3E;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,MAAI,MAAM,MAAM;AACd,UAAM,UAAU,MAAM,KAAK,4BAA4B,OACnD,MAAM,KAAK,mBACX,IAAI,KAAK,MAAM,KAAK,gBAAgB;AAExC,UAAM,MAAM,oBAAI,KAAK;AACrB,UAAM,WAAW,IAAI,QAAQ,IAAI,QAAQ,QAAQ;AACjD,UAAM,aAAa,YAAY,MAAO,KAAK,KAAK;AAGhD,QAAI,aAAa,GAAG;AAClB,YAAM,IAAI;AAAA,QACR,gEAAgE,MAAM,GAAG,qGAAqG,WAAW,QAAQ,CAAC,CAAC;AAAA,MACrM;AAAA,IACF;AAAA,EACF;AACF;;;ACjCO,SAAS,YAAY,SAAyB,UAA0B,CAAC,GAAW;AACzF,QAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,MAAI,eAAe,CAAC,GAAG,OAAO;AAE9B,MAAI,QAAQ,gBAAgB;AAC1B,iBAAa,KAAK,CAAC,GAAG,MAAM;AAC1B,YAAM,YAAY,EAAE,aAAa,SAAa,EAAE,WAAsB;AACtE,YAAM,YAAY,EAAE,aAAa,SAAa,EAAE,WAAsB;AACtE,aAAO,YAAY;AAAA,IACrB,CAAC;AAAA,EACH;AAEA,MAAI,MAAM;AAAA;AACV,SAAO;AAAA;AACP,SAAO;AAAA;AACP,SAAO;AAAA;AACP,SAAO;AAAA;AACP,SAAO;AAAA;AAEP,aAAW,SAAS,cAAc;AAEhC,wBAAoB,KAAK;AAEzB,WAAO;AAAA;AAGP,WAAO,gBAAgB,OAAO,SAAS,GAAG;AAG1C,WAAO,cAAc,MAAM,MAAM;AAGjC,WAAO,cAAc,MAAM,MAAM;AAGjC,WAAO,aAAa,MAAM,IAAI;AAE9B,WAAO;AAAA;AAAA,EACT;AAEA,SAAO;AACP,SAAO;AACT;;;AC9CO,SAAS,qBAAqB,SAAsC;AACzE,MAAI,MAAM;AAAA;AACV,SAAO;AAAA;AAEP,aAAW,SAAS,SAAS;AAC3B,UAAM,WAAW,uBAAuB,MAAM,KAAK,wBAAwB;AAE3E,WAAO;AAAA;AACP,WAAO,YAAY,UAAU,QAAQ,CAAC;AAAA;AAEtC,QAAI,MAAM,SAAS;AACjB,YAAM,OAAO,MAAM,mBAAmB,OAAO,MAAM,QAAQ,YAAY,IAAI,MAAM;AACjF,aAAO,gBAAgB,IAAI;AAAA;AAAA,IAC7B;AAEA,WAAO;AAAA;AAAA,EACT;AAEA,SAAO;AACP,SAAO;AACT;;;ARbO,SAAS,yBACd,SACA,UAA0B,CAAC,GACjB;AACV,QAAM,MAAM,YAAY,SAAS,OAAO;AAGxC,QAAM,qBAAqB,QAAQ,WAAW,SAC1C,mBAAmB,QAAQ,MAAM,sBACjC;AAEJ,SAAO,IAAI,SAAS,KAAK;AAAA,IACvB,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,iBAAiB;AAAA,IACnB;AAAA,EACF,CAAC;AACH;AAUO,SAAS,8BACd,SACA,UAA0C,CAAC,GACjC;AACV,QAAM,MAAM,qBAAqB,OAAO;AAGxC,QAAM,qBAAqB,QAAQ,WAAW,SAC1C,mBAAmB,QAAQ,MAAM,sBACjC;AAEJ,SAAO,IAAI,SAAS,KAAK;AAAA,IACvB,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,iBAAiB;AAAA,IACnB;AAAA,EACF,CAAC;AACH;","names":[]}
package/dist/index.d.cts CHANGED
@@ -148,14 +148,34 @@ interface SitemapOptions {
148
148
  */
149
149
  maxAge?: number;
150
150
  }
151
+ /**
152
+ * Interface pour une entrée individuelle dans un index de sitemaps
153
+ * @see https://developers.google.com/search/docs/crawling-indexing/sitemaps/large-sitemaps
154
+ */
155
+ interface SitemapIndexEntry {
156
+ /** URL absolue du sous-sitemap (ex: 'https://fomadev.com/sitemap-vidéos.xml') */
157
+ loc: string;
158
+ /** Date de la dernière modification du sous-sitemap */
159
+ lastmod?: string | Date;
160
+ }
151
161
 
152
162
  /**
153
163
  * Génère une réponse HTTP compatible Next.js (App Router) avec options de configuration.
154
- * v1.0.9 : Injection dynamique et personnalisable de l'en-tête Cache-Control via l'option maxAge
155
- * * @param entries - Liste des entrées du sitemap
164
+ * v1.0.9 : Injection dynamique et personnalisable de l'en-tête Cache-Control via l'option maxAge.
165
+ *
166
+ * @param entries - Liste des entrées du sitemap
156
167
  * @param options - Options de génération et de mise en cache (ex: autoLastmod, maxAge)
157
168
  * @returns Une instance de Response contenant le flux XML configuré
158
169
  */
159
170
  declare function getServerSitemapResponse(entries: SitemapEntry[], options?: SitemapOptions): Response;
171
+ /**
172
+ * ✨ v1.2.0 : Génère une réponse HTTP Next.js (App Router) pour un Index de Sitemaps.
173
+ * Permet de lister et de regrouper plusieurs sous-fichiers XML sitemaps.
174
+ *
175
+ * @param entries - Liste des sous-sitemaps composant l'index
176
+ * @param options - Options de configuration (ex: maxAge pour le cache)
177
+ * @returns Une instance de Response contenant le flux XML de l'index
178
+ */
179
+ declare function getServerSitemapIndexResponse(entries: SitemapIndexEntry[], options?: Pick<SitemapOptions, 'maxAge'>): Response;
160
180
 
161
- export { type SitemapAlternate, type SitemapChangeFreq, type SitemapEntry, type SitemapImage, type SitemapNews, type SitemapOptions, type SitemapPriority, type SitemapVideo, type VideoPlatform, type VideoPrice, type VideoRestriction, getServerSitemapResponse };
181
+ export { type SitemapAlternate, type SitemapChangeFreq, type SitemapEntry, type SitemapImage, type SitemapIndexEntry, type SitemapNews, type SitemapOptions, type SitemapPriority, type SitemapVideo, type VideoPlatform, type VideoPrice, type VideoRestriction, getServerSitemapIndexResponse, getServerSitemapResponse };
package/dist/index.d.ts CHANGED
@@ -148,14 +148,34 @@ interface SitemapOptions {
148
148
  */
149
149
  maxAge?: number;
150
150
  }
151
+ /**
152
+ * Interface pour une entrée individuelle dans un index de sitemaps
153
+ * @see https://developers.google.com/search/docs/crawling-indexing/sitemaps/large-sitemaps
154
+ */
155
+ interface SitemapIndexEntry {
156
+ /** URL absolue du sous-sitemap (ex: 'https://fomadev.com/sitemap-vidéos.xml') */
157
+ loc: string;
158
+ /** Date de la dernière modification du sous-sitemap */
159
+ lastmod?: string | Date;
160
+ }
151
161
 
152
162
  /**
153
163
  * Génère une réponse HTTP compatible Next.js (App Router) avec options de configuration.
154
- * v1.0.9 : Injection dynamique et personnalisable de l'en-tête Cache-Control via l'option maxAge
155
- * * @param entries - Liste des entrées du sitemap
164
+ * v1.0.9 : Injection dynamique et personnalisable de l'en-tête Cache-Control via l'option maxAge.
165
+ *
166
+ * @param entries - Liste des entrées du sitemap
156
167
  * @param options - Options de génération et de mise en cache (ex: autoLastmod, maxAge)
157
168
  * @returns Une instance de Response contenant le flux XML configuré
158
169
  */
159
170
  declare function getServerSitemapResponse(entries: SitemapEntry[], options?: SitemapOptions): Response;
171
+ /**
172
+ * ✨ v1.2.0 : Génère une réponse HTTP Next.js (App Router) pour un Index de Sitemaps.
173
+ * Permet de lister et de regrouper plusieurs sous-fichiers XML sitemaps.
174
+ *
175
+ * @param entries - Liste des sous-sitemaps composant l'index
176
+ * @param options - Options de configuration (ex: maxAge pour le cache)
177
+ * @returns Une instance de Response contenant le flux XML de l'index
178
+ */
179
+ declare function getServerSitemapIndexResponse(entries: SitemapIndexEntry[], options?: Pick<SitemapOptions, 'maxAge'>): Response;
160
180
 
161
- export { type SitemapAlternate, type SitemapChangeFreq, type SitemapEntry, type SitemapImage, type SitemapNews, type SitemapOptions, type SitemapPriority, type SitemapVideo, type VideoPlatform, type VideoPrice, type VideoRestriction, getServerSitemapResponse };
181
+ export { type SitemapAlternate, type SitemapChangeFreq, type SitemapEntry, type SitemapImage, type SitemapIndexEntry, type SitemapNews, type SitemapOptions, type SitemapPriority, type SitemapVideo, type VideoPlatform, type VideoPrice, type VideoRestriction, getServerSitemapIndexResponse, getServerSitemapResponse };
package/dist/index.js CHANGED
@@ -396,6 +396,30 @@ function generateXml(entries, options = {}) {
396
396
  return xml;
397
397
  }
398
398
 
399
+ // src/core/builders/index-builder.ts
400
+ function buildSitemapIndexXml(entries) {
401
+ let xml = `<?xml version="1.0" encoding="UTF-8"?>
402
+ `;
403
+ xml += `<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
404
+ `;
405
+ for (const entry of entries) {
406
+ const cleanLoc = sanitizeAndValidateUrl(entry.loc, "sitemap index location");
407
+ xml += ` <sitemap>
408
+ `;
409
+ xml += ` <loc>${escapeXml(cleanLoc)}</loc>
410
+ `;
411
+ if (entry.lastmod) {
412
+ const date = entry.lastmod instanceof Date ? entry.lastmod.toISOString() : entry.lastmod;
413
+ xml += ` <lastmod>${date}</lastmod>
414
+ `;
415
+ }
416
+ xml += ` </sitemap>
417
+ `;
418
+ }
419
+ xml += `</sitemapindex>`;
420
+ return xml;
421
+ }
422
+
399
423
  // src/index.ts
400
424
  function getServerSitemapResponse(entries, options = {}) {
401
425
  const xml = generateXml(entries, options);
@@ -407,7 +431,18 @@ function getServerSitemapResponse(entries, options = {}) {
407
431
  }
408
432
  });
409
433
  }
434
+ function getServerSitemapIndexResponse(entries, options = {}) {
435
+ const xml = buildSitemapIndexXml(entries);
436
+ const cacheControlHeader = options.maxAge !== void 0 ? `public, max-age=${options.maxAge}, must-revalidate` : "public, s-maxage=86400, stale-while-revalidate";
437
+ return new Response(xml, {
438
+ headers: {
439
+ "Content-Type": "application/xml",
440
+ "Cache-Control": cacheControlHeader
441
+ }
442
+ });
443
+ }
410
444
  export {
445
+ getServerSitemapIndexResponse,
411
446
  getServerSitemapResponse
412
447
  };
413
448
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils/xml-escape.ts","../src/core/builders/url-builder.ts","../src/core/builders/image-builder.ts","../src/core/builders/video-builder.ts","../src/core/builders/news-builder.ts","../src/core/validation/cross-validator.ts","../src/core/generator.ts","../src/index.ts"],"sourcesContent":["/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\n/**\n * Convertit les caractères spéciaux en entités XML pour éviter la corruption du fichier.\n * Gère : <, >, &, \", '\n */\nexport function escapeXml(unsafe: string | undefined | null): string {\n if (!unsafe) return '';\n \n return unsafe.replace(/[<>&\"']/g, (c) => {\n switch (c) {\n case '<': return '&lt;';\n case '>': return '&gt;';\n case '&': return '&amp;';\n case '\"': return '&quot;';\n case \"'\": return '&apos;';\n default: return c;\n }\n });\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry, SitemapOptions } from '../../types/sitemap.js';\nimport { escapeXml } from '../../utils/xml-escape.js';\n\n/**\n * Nettoie et valide de manière stricte le format et la structure d'une URL.\n * v1.0.7 : Intégration de l'Auto-Trimming (nettoyage des espaces de début et de fin)\n */\nexport function sanitizeAndValidateUrl(rawUrl: string, context: string): string {\n const url = rawUrl ? rawUrl.trim() : '';\n\n if (!url.startsWith('http://') && !url.startsWith('https://')) {\n throw new Error(\n `[next-advanced-sitemap] Invalid URL in ${context}: \"${url}\". URLs must start with http:// or https://`\n );\n }\n\n if (url.includes(' ')) {\n throw new Error(\n `[next-advanced-sitemap] Malformed URL structure detected in ${context}: \"${url}\". Please verify spaces or special characters.`\n );\n }\n\n let isValid = false;\n if (typeof URL.canParse === 'function') {\n isValid = URL.canParse(url);\n } else {\n try {\n new URL(url);\n isValid = true;\n } catch {\n isValid = false;\n }\n }\n\n if (!isValid) {\n throw new Error(\n `[next-advanced-sitemap] Malformed URL structure detected in ${context}: \"${url}\". Please verify spaces or special characters.`\n );\n }\n\n return url;\n}\n\n/**\n * Génère le bloc XML de base pour un nœud URL (loc, alternates, lastmod, changefreq, priority).\n */\nexport function buildUrlBaseXml(entry: SitemapEntry, options: SitemapOptions, nowIso: string): string {\n let xml = '';\n \n const cleanMainUrl = sanitizeAndValidateUrl(entry.url, 'main entry');\n xml += ` <loc>${escapeXml(cleanMainUrl)}</loc>\\n`;\n\n if (entry.alternates?.length) {\n for (const alt of entry.alternates) {\n const cleanAltUrl = sanitizeAndValidateUrl(alt.href, 'alternate link');\n xml += ` <xhtml:link rel=\"alternate\" hreflang=\"${escapeXml(alt.hreflang)}\" href=\"${escapeXml(cleanAltUrl)}\" />\\n`;\n }\n }\n\n let lastmodValue = entry.lastmod;\n if (options.autoLastmod && !lastmodValue) {\n lastmodValue = nowIso;\n }\n\n if (lastmodValue) {\n const date = lastmodValue instanceof Date ? lastmodValue.toISOString() : lastmodValue;\n xml += ` <lastmod>${date}</lastmod>\\n`;\n }\n\n if (entry.changefreq) {\n xml += ` <changefreq>${entry.changefreq}</changefreq>\\n`;\n }\n\n if (entry.priority !== undefined) {\n xml += ` <priority>${(entry.priority as number).toFixed(1)}</priority>\\n`;\n }\n\n return xml;\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry } from '../../types/sitemap.js';\nimport { escapeXml } from '../../utils/xml-escape.js';\nimport { sanitizeAndValidateUrl } from './url-builder.js';\n\nexport function buildImageXml(images: SitemapEntry['images']): string {\n if (!images?.length) return '';\n\n let xml = '';\n for (const img of images) {\n const cleanImgUrl = sanitizeAndValidateUrl(img.loc, 'image location');\n \n xml += ` <image:image>\\n`;\n xml += ` <image:loc>${escapeXml(cleanImgUrl)}</image:loc>\\n`;\n \n if (img.title && img.title.trim() !== '') {\n xml += ` <image:title>${escapeXml(img.title.trim())}</image:title>\\n`;\n }\n if (img.caption && img.caption.trim() !== '') {\n xml += ` <image:caption>${escapeXml(img.caption.trim())}</image:caption>\\n`;\n }\n \n if (img.geo_location && img.geo_location.trim() !== '') {\n xml += ` <image:geo_location>${escapeXml(img.geo_location.trim())}</image:geo_location>\\n`;\n }\n \n if (img.license) {\n const cleanLicenseUrl = sanitizeAndValidateUrl(img.license, 'image license URL');\n xml += ` <image:license>${escapeXml(cleanLicenseUrl)}</image:license>\\n`;\n }\n \n xml += ` </image:image>\\n`;\n }\n \n return xml;\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry } from '../../types/sitemap.js';\nimport { escapeXml } from '../../utils/xml-escape.js';\nimport { sanitizeAndValidateUrl } from './url-builder.js';\n\nexport function buildVideoXml(videos: SitemapEntry['videos']): string {\n if (!videos?.length) return '';\n\n let xml = '';\n for (const vid of videos) {\n const cleanThumbLoc = sanitizeAndValidateUrl(vid.thumbnail_loc, 'video thumbnail');\n const cleanContentLoc = vid.content_loc ? sanitizeAndValidateUrl(vid.content_loc, 'video content location') : undefined;\n const cleanPlayerLoc = vid.player_loc ? sanitizeAndValidateUrl(vid.player_loc, 'video player location') : undefined;\n\n xml += ` <video:video>\\n`;\n xml += ` <video:thumbnail_loc>${escapeXml(cleanThumbLoc)}</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 \n if (cleanContentLoc) xml += ` <video:content_loc>${escapeXml(cleanContentLoc)}</video:content_loc>\\n`;\n if (cleanPlayerLoc) xml += ` <video:player_loc>${escapeXml(cleanPlayerLoc)}</video:player_loc>\\n`;\n \n if (vid.publication_date) {\n const vDate = vid.publication_date instanceof Date ? vid.publication_date.toISOString() : vid.publication_date;\n xml += ` <video:publication_date>${vDate}</video:publication_date>\\n`;\n }\n\n // ✨ Validation et Sérialisation de la durée (0 - 28800s)\n if (vid.duration !== undefined) {\n const finalDuration = Math.floor(vid.duration);\n if (finalDuration < 0 || finalDuration > 28800) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video duration: ${finalDuration}. Duration must be an integer between 0 and 28800 seconds (8 hours).`\n );\n }\n xml += ` <video:duration>${finalDuration}</video:duration>\\n`;\n }\n\n // ✨ Validation et Sérialisation du nombre de vues (>= 0)\n if (vid.view_count !== undefined) {\n const finalViewCount = Math.floor(vid.view_count);\n if (finalViewCount < 0) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video view_count: ${finalViewCount}. View count cannot be negative.`\n );\n }\n xml += ` <video:view_count>${finalViewCount}</video:view_count>\\n`;\n }\n\n if (vid.live) {\n xml += ` <video:live>${vid.live}</video:live>\\n`;\n }\n\n // ✨ Validation et Sérialisation des Restrictions Pays (v1.1.4)\n if (vid.restriction) {\n if (!vid.restriction.countries || vid.restriction.countries.length === 0) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video restriction: countries array cannot be empty.`\n );\n }\n\n const cleanCountries = vid.restriction.countries.map(country => {\n const code = country.trim().toUpperCase();\n if (code.length < 2 || code.length > 3) {\n throw new Error(\n `[next-advanced-sitemap] Invalid ISO country code detected: \"${country}\". Must be a valid ISO 3166 code.`\n );\n }\n return code;\n });\n\n const countriesStr = cleanCountries.join(' ');\n xml += ` <video:restriction relationship=\"${vid.restriction.relationship}\">${countriesStr}</video:restriction>\\n`;\n }\n\n // ✨ Validation et Sérialisation des Plateformes (v1.1.4)\n if (vid.platform) {\n if (!vid.platform.platforms || vid.platform.platforms.length === 0) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video platform: platforms array cannot be empty.`\n );\n }\n\n const validPlatforms = ['web', 'mobile', 'tv'];\n for (const p of vid.platform.platforms) {\n if (!validPlatforms.includes(p)) {\n throw new Error(\n `[next-advanced-sitemap] Invalid platform type: \"${p}\". Allowed values are 'web', 'mobile', or 'tv'.`\n );\n }\n }\n\n const platformsStr = vid.platform.platforms.join(' ');\n xml += ` <video:platform relationship=\"${vid.platform.relationship}\">${platformsStr}</video:platform>\\n`;\n }\n \n if (vid.requires_subscription !== undefined) {\n let subValue: 'yes' | 'no';\n\n if (typeof vid.requires_subscription === 'boolean') {\n subValue = vid.requires_subscription ? 'yes' : 'no';\n } else if (vid.requires_subscription === 'yes' || vid.requires_subscription === 'no') {\n subValue = vid.requires_subscription;\n } else {\n throw new Error(\n `[next-advanced-sitemap] Invalid value for requires_subscription: \"${vid.requires_subscription}\". Expected boolean or strict string 'yes' | 'no'.`\n );\n }\n\n xml += ` <video:requires_subscription>${subValue}</video:requires_subscription>\\n`;\n }\n\n // ✨ Validation et Sérialisation des Prix et Achats (v1.1.6)\n if (vid.price) {\n const { value, currency, type } = vid.price;\n\n if (value === undefined || value < 0) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video price value: \"${value}\". Value must be a positive number.`\n );\n }\n\n const cleanCurrency = currency ? currency.trim().toUpperCase() : '';\n if (cleanCurrency.length !== 3) {\n throw new Error(\n `[next-advanced-sitemap] Invalid ISO 4217 currency code: \"${currency}\". Currency must be exactly a 3-letter code.`\n );\n }\n\n let priceXml = ` <video:price currency=\"${cleanCurrency}\"`;\n if (type) {\n if (type !== 'rent' && type !== 'own') {\n throw new Error(\n `[next-advanced-sitemap] Invalid price type: \"${type}\". Allowed values are 'rent' or 'own'.`\n );\n }\n priceXml += ` type=\"${type}\"`;\n }\n priceXml += `>${value.toFixed(2)}</video:price>\\n`;\n \n xml += priceXml;\n }\n\n // ✨ Validation et Sérialisation de la Catégorie (v1.1.7)\n if (vid.category !== undefined) {\n const cleanCategory = vid.category.trim();\n if (!cleanCategory) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video category: category cannot be empty or just whitespaces.`\n );\n }\n if (cleanCategory.length > 256) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video category length: ${cleanCategory.length}. Maximum allowed is 256 characters.`\n );\n }\n xml += ` <video:category>${escapeXml(cleanCategory)}</video:category>\\n`;\n }\n\n // ✨ Validation et Sérialisation des Tags (v1.1.7)\n if (vid.tags) {\n if (vid.tags.length > 32) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video tags count: ${vid.tags.length}. A video can have a maximum of 32 tags.`\n );\n }\n\n for (const tag of vid.tags) {\n const cleanTag = tag.trim();\n if (!cleanTag) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video tag detected: tag cannot be empty or just whitespaces.`\n );\n }\n xml += ` <video:tag>${escapeXml(cleanTag)}</video:tag>\\n`;\n }\n }\n\n xml += ` </video:video>\\n`;\n }\n \n return xml;\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry } from '../../types/sitemap.js';\nimport { escapeXml } from '../../utils/xml-escape.js';\n\nexport function buildNewsXml(news: SitemapEntry['news']): string {\n if (!news) return '';\n\n const nDate = news.publication_date instanceof Date ? news.publication_date.toISOString() : news.publication_date;\n \n let xml = '';\n xml += ` <news:news>\\n`;\n xml += ` <news:publication>\\n`;\n xml += ` <news:name>${escapeXml(news.name)}</news:name>\\n`;\n xml += ` <news:language>${escapeXml(news.language)}</news:language>\\n`;\n xml += ` </news:publication>\\n`;\n xml += ` <news:publication_date>${nDate}</news:publication_date>\\n`;\n xml += ` <news:title>${escapeXml(news.title)}</news:title>\\n`;\n\n // ✨ Validation et Sérialisation des Stock Tickers (v1.1.8)\n if (news.stock_tickers) {\n if (!Array.isArray(news.stock_tickers)) {\n throw new Error(\n `[next-advanced-sitemap] Invalid news stock_tickers: property must be an array of strings.`\n );\n }\n\n const validatedTickers = news.stock_tickers\n .map(ticker => {\n const cleanTicker = ticker.trim();\n if (!cleanTicker) {\n throw new Error(\n `[next-advanced-sitemap] Invalid stock ticker detected: ticker element cannot be empty or just whitespaces.`\n );\n }\n if (!cleanTicker.includes(':')) {\n throw new Error(\n `[next-advanced-sitemap] Invalid stock ticker format: \"${cleanTicker}\". Expected \"EXCHANGE:TICKER\" format (e.g., \"NASDAQ:AAPL\").`\n );\n }\n return cleanTicker;\n });\n\n if (validatedTickers.length > 0) {\n // Google demande une liste séparée par des virgules (sans espaces superflus)\n const tickersString = validatedTickers.join(',');\n xml += ` <news:stock_tickers>${escapeXml(tickersString)}</news:stock_tickers>\\n`;\n }\n }\n\n xml += ` </news:news>\\n`;\n \n return xml;\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry } from '../../types/sitemap.js';\n\n/**\n * Valide la cohérence logique et sémantique croisée entre les différents champs d'une entrée.\n * Garantit un score SEO Search Console parfait.\n */\nexport function validateCrossFields(entry: SitemapEntry): void {\n // 1. Validations croisées sur l'extension Vidéo\n if (entry.videos && entry.videos.length > 0) {\n for (const vid of entry.videos) {\n \n // RÈGLE A : Conflit d'accès payant vs abonnement\n if (vid.live === 'yes' && vid.duration !== undefined && vid.duration > 0) {\n throw new Error(\n `[next-advanced-sitemap] Cross-field validation error on URL \"${entry.url}\": A live video stream cannot have a pre-defined static duration.`\n );\n }\n\n // RÈGLE B : Conflit d'abonnement requis vs Prix d'achat direct sans logique transactionnelle définie\n if (vid.requires_subscription === 'yes' && vid.price && vid.price.type === 'own') {\n throw new Error(\n `[next-advanced-sitemap] Cross-field validation error on URL \"${entry.url}\": Video cannot simultaneously require a global subscription and be available for full individual ownership (\"own\").`\n );\n }\n }\n }\n\n // 2. Validations croisées sur l'extension Google News\n if (entry.news) {\n const pubDate = entry.news.publication_date instanceof Date \n ? entry.news.publication_date \n : new Date(entry.news.publication_date);\n\n const now = new Date();\n const diffInMs = now.getTime() - pubDate.getTime();\n const diffInDays = diffInMs / (1000 * 60 * 60 * 24);\n\n // RÈGLE C : Google News n'indexe pas les articles de plus de 2 jours (48 heures) via sitemap\n if (diffInDays > 2) {\n throw new Error(\n `[next-advanced-sitemap] Cross-field validation error on URL \"${entry.url}\": Google News sitemaps only support articles published within the last 48 hours. Article date is ${diffInDays.toFixed(1)} days old.`\n );\n }\n }\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry, SitemapOptions } from '../types/sitemap.js';\nimport { buildUrlBaseXml } from './builders/url-builder.js';\nimport { buildImageXml } from './builders/image-builder.js';\nimport { buildVideoXml } from './builders/video-builder.js';\nimport { buildNewsXml } from './builders/news-builder.js';\nimport { validateCrossFields } from './validation/cross-validator.js';\n\n/**\n * Génère le flux XML complet du sitemap incluant les extensions Images, Vidéos, News et Hreflang.\n * v1.1.9 : Intégration de la validation sémantique croisée en amont.\n */\nexport function generateXml(entries: SitemapEntry[], options: SitemapOptions = {}): string {\n const now = new Date().toISOString();\n let finalEntries = [...entries];\n\n if (options.sortByPriority) {\n finalEntries.sort((a, b) => {\n const priorityA = a.priority !== undefined ? (a.priority as number) : 0.5;\n const priorityB = b.priority !== undefined ? (b.priority as number) : 0.5;\n return priorityB - priorityA;\n });\n }\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`;\n xml += ` xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\"\\n`;\n xml += ` xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\"\\n`;\n xml += ` xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\"\\n`;\n xml += ` xmlns:xhtml=\"http://www.w3.org/1999/xhtml\">\\n`;\n\n for (const entry of finalEntries) {\n // 🔥 ÉTAPE CRUCIALE v1.1.9 : Validation croisée pré-génération\n validateCrossFields(entry);\n\n xml += ` <url>\\n`;\n \n // 1. Éléments de base et hreflang alternatifs\n xml += buildUrlBaseXml(entry, options, now);\n\n // 2. Extension Images Google\n xml += buildImageXml(entry.images);\n\n // 3. Extension Vidéos Google (Validations v1.1.3 & v1.1.4 intégrées)\n xml += buildVideoXml(entry.videos);\n\n // 4. Extension News Google\n xml += buildNewsXml(entry.news);\n\n xml += ` </url>\\n`;\n }\n\n xml += `</urlset>`;\n return xml;\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry, SitemapOptions } from './types/sitemap.js';\nimport { generateXml } from './core/generator.js';\n\nexport * from './types/sitemap.js';\n\n/**\n * Génère une réponse HTTP compatible Next.js (App Router) avec options de configuration.\n * v1.0.9 : Injection dynamique et personnalisable de l'en-tête Cache-Control via l'option maxAge\n * * @param entries - Liste des entrées du sitemap\n * @param options - Options de génération et de mise en cache (ex: autoLastmod, maxAge)\n * @returns Une instance de Response contenant le flux XML configuré\n */\nexport function getServerSitemapResponse(\n entries: SitemapEntry[], \n options: SitemapOptions = {}\n): Response {\n const xml = generateXml(entries, options);\n\n // Détermination de la stratégie de mise en cache (v1.0.9)\n const cacheControlHeader = options.maxAge !== undefined\n ? `public, max-age=${options.maxAge}, must-revalidate`\n : 'public, s-maxage=86400, stale-while-revalidate';\n\n return new Response(xml, {\n headers: {\n 'Content-Type': 'application/xml',\n 'Cache-Control': cacheControlHeader,\n },\n });\n}"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/utils/xml-escape.ts","../src/core/builders/url-builder.ts","../src/core/builders/image-builder.ts","../src/core/builders/video-builder.ts","../src/core/builders/news-builder.ts","../src/core/validation/cross-validator.ts","../src/core/generator.ts","../src/core/builders/index-builder.ts","../src/index.ts"],"sourcesContent":["/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\n/**\n * Convertit les caractères spéciaux en entités XML pour éviter la corruption du fichier.\n * Gère : <, >, &, \", '\n */\nexport function escapeXml(unsafe: string | undefined | null): string {\n if (!unsafe) return '';\n \n return unsafe.replace(/[<>&\"']/g, (c) => {\n switch (c) {\n case '<': return '&lt;';\n case '>': return '&gt;';\n case '&': return '&amp;';\n case '\"': return '&quot;';\n case \"'\": return '&apos;';\n default: return c;\n }\n });\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry, SitemapOptions } from '../../types/sitemap.js';\nimport { escapeXml } from '../../utils/xml-escape.js';\n\n/**\n * Nettoie et valide de manière stricte le format et la structure d'une URL.\n * v1.0.7 : Intégration de l'Auto-Trimming (nettoyage des espaces de début et de fin)\n */\nexport function sanitizeAndValidateUrl(rawUrl: string, context: string): string {\n const url = rawUrl ? rawUrl.trim() : '';\n\n if (!url.startsWith('http://') && !url.startsWith('https://')) {\n throw new Error(\n `[next-advanced-sitemap] Invalid URL in ${context}: \"${url}\". URLs must start with http:// or https://`\n );\n }\n\n if (url.includes(' ')) {\n throw new Error(\n `[next-advanced-sitemap] Malformed URL structure detected in ${context}: \"${url}\". Please verify spaces or special characters.`\n );\n }\n\n let isValid = false;\n if (typeof URL.canParse === 'function') {\n isValid = URL.canParse(url);\n } else {\n try {\n new URL(url);\n isValid = true;\n } catch {\n isValid = false;\n }\n }\n\n if (!isValid) {\n throw new Error(\n `[next-advanced-sitemap] Malformed URL structure detected in ${context}: \"${url}\". Please verify spaces or special characters.`\n );\n }\n\n return url;\n}\n\n/**\n * Génère le bloc XML de base pour un nœud URL (loc, alternates, lastmod, changefreq, priority).\n */\nexport function buildUrlBaseXml(entry: SitemapEntry, options: SitemapOptions, nowIso: string): string {\n let xml = '';\n \n const cleanMainUrl = sanitizeAndValidateUrl(entry.url, 'main entry');\n xml += ` <loc>${escapeXml(cleanMainUrl)}</loc>\\n`;\n\n if (entry.alternates?.length) {\n for (const alt of entry.alternates) {\n const cleanAltUrl = sanitizeAndValidateUrl(alt.href, 'alternate link');\n xml += ` <xhtml:link rel=\"alternate\" hreflang=\"${escapeXml(alt.hreflang)}\" href=\"${escapeXml(cleanAltUrl)}\" />\\n`;\n }\n }\n\n let lastmodValue = entry.lastmod;\n if (options.autoLastmod && !lastmodValue) {\n lastmodValue = nowIso;\n }\n\n if (lastmodValue) {\n const date = lastmodValue instanceof Date ? lastmodValue.toISOString() : lastmodValue;\n xml += ` <lastmod>${date}</lastmod>\\n`;\n }\n\n if (entry.changefreq) {\n xml += ` <changefreq>${entry.changefreq}</changefreq>\\n`;\n }\n\n if (entry.priority !== undefined) {\n xml += ` <priority>${(entry.priority as number).toFixed(1)}</priority>\\n`;\n }\n\n return xml;\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry } from '../../types/sitemap.js';\nimport { escapeXml } from '../../utils/xml-escape.js';\nimport { sanitizeAndValidateUrl } from './url-builder.js';\n\nexport function buildImageXml(images: SitemapEntry['images']): string {\n if (!images?.length) return '';\n\n let xml = '';\n for (const img of images) {\n const cleanImgUrl = sanitizeAndValidateUrl(img.loc, 'image location');\n \n xml += ` <image:image>\\n`;\n xml += ` <image:loc>${escapeXml(cleanImgUrl)}</image:loc>\\n`;\n \n if (img.title && img.title.trim() !== '') {\n xml += ` <image:title>${escapeXml(img.title.trim())}</image:title>\\n`;\n }\n if (img.caption && img.caption.trim() !== '') {\n xml += ` <image:caption>${escapeXml(img.caption.trim())}</image:caption>\\n`;\n }\n \n if (img.geo_location && img.geo_location.trim() !== '') {\n xml += ` <image:geo_location>${escapeXml(img.geo_location.trim())}</image:geo_location>\\n`;\n }\n \n if (img.license) {\n const cleanLicenseUrl = sanitizeAndValidateUrl(img.license, 'image license URL');\n xml += ` <image:license>${escapeXml(cleanLicenseUrl)}</image:license>\\n`;\n }\n \n xml += ` </image:image>\\n`;\n }\n \n return xml;\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry } from '../../types/sitemap.js';\nimport { escapeXml } from '../../utils/xml-escape.js';\nimport { sanitizeAndValidateUrl } from './url-builder.js';\n\nexport function buildVideoXml(videos: SitemapEntry['videos']): string {\n if (!videos?.length) return '';\n\n let xml = '';\n for (const vid of videos) {\n const cleanThumbLoc = sanitizeAndValidateUrl(vid.thumbnail_loc, 'video thumbnail');\n const cleanContentLoc = vid.content_loc ? sanitizeAndValidateUrl(vid.content_loc, 'video content location') : undefined;\n const cleanPlayerLoc = vid.player_loc ? sanitizeAndValidateUrl(vid.player_loc, 'video player location') : undefined;\n\n xml += ` <video:video>\\n`;\n xml += ` <video:thumbnail_loc>${escapeXml(cleanThumbLoc)}</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 \n if (cleanContentLoc) xml += ` <video:content_loc>${escapeXml(cleanContentLoc)}</video:content_loc>\\n`;\n if (cleanPlayerLoc) xml += ` <video:player_loc>${escapeXml(cleanPlayerLoc)}</video:player_loc>\\n`;\n \n if (vid.publication_date) {\n const vDate = vid.publication_date instanceof Date ? vid.publication_date.toISOString() : vid.publication_date;\n xml += ` <video:publication_date>${vDate}</video:publication_date>\\n`;\n }\n\n // ✨ Validation et Sérialisation de la durée (0 - 28800s)\n if (vid.duration !== undefined) {\n const finalDuration = Math.floor(vid.duration);\n if (finalDuration < 0 || finalDuration > 28800) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video duration: ${finalDuration}. Duration must be an integer between 0 and 28800 seconds (8 hours).`\n );\n }\n xml += ` <video:duration>${finalDuration}</video:duration>\\n`;\n }\n\n // ✨ Validation et Sérialisation du nombre de vues (>= 0)\n if (vid.view_count !== undefined) {\n const finalViewCount = Math.floor(vid.view_count);\n if (finalViewCount < 0) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video view_count: ${finalViewCount}. View count cannot be negative.`\n );\n }\n xml += ` <video:view_count>${finalViewCount}</video:view_count>\\n`;\n }\n\n if (vid.live) {\n xml += ` <video:live>${vid.live}</video:live>\\n`;\n }\n\n // ✨ Validation et Sérialisation des Restrictions Pays (v1.1.4)\n if (vid.restriction) {\n if (!vid.restriction.countries || vid.restriction.countries.length === 0) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video restriction: countries array cannot be empty.`\n );\n }\n\n const cleanCountries = vid.restriction.countries.map(country => {\n const code = country.trim().toUpperCase();\n if (code.length < 2 || code.length > 3) {\n throw new Error(\n `[next-advanced-sitemap] Invalid ISO country code detected: \"${country}\". Must be a valid ISO 3166 code.`\n );\n }\n return code;\n });\n\n const countriesStr = cleanCountries.join(' ');\n xml += ` <video:restriction relationship=\"${vid.restriction.relationship}\">${countriesStr}</video:restriction>\\n`;\n }\n\n // ✨ Validation et Sérialisation des Plateformes (v1.1.4)\n if (vid.platform) {\n if (!vid.platform.platforms || vid.platform.platforms.length === 0) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video platform: platforms array cannot be empty.`\n );\n }\n\n const validPlatforms = ['web', 'mobile', 'tv'];\n for (const p of vid.platform.platforms) {\n if (!validPlatforms.includes(p)) {\n throw new Error(\n `[next-advanced-sitemap] Invalid platform type: \"${p}\". Allowed values are 'web', 'mobile', or 'tv'.`\n );\n }\n }\n\n const platformsStr = vid.platform.platforms.join(' ');\n xml += ` <video:platform relationship=\"${vid.platform.relationship}\">${platformsStr}</video:platform>\\n`;\n }\n \n if (vid.requires_subscription !== undefined) {\n let subValue: 'yes' | 'no';\n\n if (typeof vid.requires_subscription === 'boolean') {\n subValue = vid.requires_subscription ? 'yes' : 'no';\n } else if (vid.requires_subscription === 'yes' || vid.requires_subscription === 'no') {\n subValue = vid.requires_subscription;\n } else {\n throw new Error(\n `[next-advanced-sitemap] Invalid value for requires_subscription: \"${vid.requires_subscription}\". Expected boolean or strict string 'yes' | 'no'.`\n );\n }\n\n xml += ` <video:requires_subscription>${subValue}</video:requires_subscription>\\n`;\n }\n\n // ✨ Validation et Sérialisation des Prix et Achats (v1.1.6)\n if (vid.price) {\n const { value, currency, type } = vid.price;\n\n if (value === undefined || value < 0) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video price value: \"${value}\". Value must be a positive number.`\n );\n }\n\n const cleanCurrency = currency ? currency.trim().toUpperCase() : '';\n if (cleanCurrency.length !== 3) {\n throw new Error(\n `[next-advanced-sitemap] Invalid ISO 4217 currency code: \"${currency}\". Currency must be exactly a 3-letter code.`\n );\n }\n\n let priceXml = ` <video:price currency=\"${cleanCurrency}\"`;\n if (type) {\n if (type !== 'rent' && type !== 'own') {\n throw new Error(\n `[next-advanced-sitemap] Invalid price type: \"${type}\". Allowed values are 'rent' or 'own'.`\n );\n }\n priceXml += ` type=\"${type}\"`;\n }\n priceXml += `>${value.toFixed(2)}</video:price>\\n`;\n \n xml += priceXml;\n }\n\n // ✨ Validation et Sérialisation de la Catégorie (v1.1.7)\n if (vid.category !== undefined) {\n const cleanCategory = vid.category.trim();\n if (!cleanCategory) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video category: category cannot be empty or just whitespaces.`\n );\n }\n if (cleanCategory.length > 256) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video category length: ${cleanCategory.length}. Maximum allowed is 256 characters.`\n );\n }\n xml += ` <video:category>${escapeXml(cleanCategory)}</video:category>\\n`;\n }\n\n // ✨ Validation et Sérialisation des Tags (v1.1.7)\n if (vid.tags) {\n if (vid.tags.length > 32) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video tags count: ${vid.tags.length}. A video can have a maximum of 32 tags.`\n );\n }\n\n for (const tag of vid.tags) {\n const cleanTag = tag.trim();\n if (!cleanTag) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video tag detected: tag cannot be empty or just whitespaces.`\n );\n }\n xml += ` <video:tag>${escapeXml(cleanTag)}</video:tag>\\n`;\n }\n }\n\n xml += ` </video:video>\\n`;\n }\n \n return xml;\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry } from '../../types/sitemap.js';\nimport { escapeXml } from '../../utils/xml-escape.js';\n\nexport function buildNewsXml(news: SitemapEntry['news']): string {\n if (!news) return '';\n\n const nDate = news.publication_date instanceof Date ? news.publication_date.toISOString() : news.publication_date;\n \n let xml = '';\n xml += ` <news:news>\\n`;\n xml += ` <news:publication>\\n`;\n xml += ` <news:name>${escapeXml(news.name)}</news:name>\\n`;\n xml += ` <news:language>${escapeXml(news.language)}</news:language>\\n`;\n xml += ` </news:publication>\\n`;\n xml += ` <news:publication_date>${nDate}</news:publication_date>\\n`;\n xml += ` <news:title>${escapeXml(news.title)}</news:title>\\n`;\n\n // ✨ Validation et Sérialisation des Stock Tickers (v1.1.8)\n if (news.stock_tickers) {\n if (!Array.isArray(news.stock_tickers)) {\n throw new Error(\n `[next-advanced-sitemap] Invalid news stock_tickers: property must be an array of strings.`\n );\n }\n\n const validatedTickers = news.stock_tickers\n .map(ticker => {\n const cleanTicker = ticker.trim();\n if (!cleanTicker) {\n throw new Error(\n `[next-advanced-sitemap] Invalid stock ticker detected: ticker element cannot be empty or just whitespaces.`\n );\n }\n if (!cleanTicker.includes(':')) {\n throw new Error(\n `[next-advanced-sitemap] Invalid stock ticker format: \"${cleanTicker}\". Expected \"EXCHANGE:TICKER\" format (e.g., \"NASDAQ:AAPL\").`\n );\n }\n return cleanTicker;\n });\n\n if (validatedTickers.length > 0) {\n // Google demande une liste séparée par des virgules (sans espaces superflus)\n const tickersString = validatedTickers.join(',');\n xml += ` <news:stock_tickers>${escapeXml(tickersString)}</news:stock_tickers>\\n`;\n }\n }\n\n xml += ` </news:news>\\n`;\n \n return xml;\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry } from '../../types/sitemap.js';\n\n/**\n * Valide la cohérence logique et sémantique croisée entre les différents champs d'une entrée.\n * Garantit un score SEO Search Console parfait.\n */\nexport function validateCrossFields(entry: SitemapEntry): void {\n // 1. Validations croisées sur l'extension Vidéo\n if (entry.videos && entry.videos.length > 0) {\n for (const vid of entry.videos) {\n \n // RÈGLE A : Conflit d'accès payant vs abonnement\n if (vid.live === 'yes' && vid.duration !== undefined && vid.duration > 0) {\n throw new Error(\n `[next-advanced-sitemap] Cross-field validation error on URL \"${entry.url}\": A live video stream cannot have a pre-defined static duration.`\n );\n }\n\n // RÈGLE B : Conflit d'abonnement requis vs Prix d'achat direct sans logique transactionnelle définie\n if (vid.requires_subscription === 'yes' && vid.price && vid.price.type === 'own') {\n throw new Error(\n `[next-advanced-sitemap] Cross-field validation error on URL \"${entry.url}\": Video cannot simultaneously require a global subscription and be available for full individual ownership (\"own\").`\n );\n }\n }\n }\n\n // 2. Validations croisées sur l'extension Google News\n if (entry.news) {\n const pubDate = entry.news.publication_date instanceof Date \n ? entry.news.publication_date \n : new Date(entry.news.publication_date);\n\n const now = new Date();\n const diffInMs = now.getTime() - pubDate.getTime();\n const diffInDays = diffInMs / (1000 * 60 * 60 * 24);\n\n // RÈGLE C : Google News n'indexe pas les articles de plus de 2 jours (48 heures) via sitemap\n if (diffInDays > 2) {\n throw new Error(\n `[next-advanced-sitemap] Cross-field validation error on URL \"${entry.url}\": Google News sitemaps only support articles published within the last 48 hours. Article date is ${diffInDays.toFixed(1)} days old.`\n );\n }\n }\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry, SitemapOptions } from '../types/sitemap.js';\nimport { buildUrlBaseXml } from './builders/url-builder.js';\nimport { buildImageXml } from './builders/image-builder.js';\nimport { buildVideoXml } from './builders/video-builder.js';\nimport { buildNewsXml } from './builders/news-builder.js';\nimport { validateCrossFields } from './validation/cross-validator.js';\n\n/**\n * Génère le flux XML complet du sitemap incluant les extensions Images, Vidéos, News et Hreflang.\n * v1.1.9 : Intégration de la validation sémantique croisée en amont.\n */\nexport function generateXml(entries: SitemapEntry[], options: SitemapOptions = {}): string {\n const now = new Date().toISOString();\n let finalEntries = [...entries];\n\n if (options.sortByPriority) {\n finalEntries.sort((a, b) => {\n const priorityA = a.priority !== undefined ? (a.priority as number) : 0.5;\n const priorityB = b.priority !== undefined ? (b.priority as number) : 0.5;\n return priorityB - priorityA;\n });\n }\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`;\n xml += ` xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\"\\n`;\n xml += ` xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\"\\n`;\n xml += ` xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\"\\n`;\n xml += ` xmlns:xhtml=\"http://www.w3.org/1999/xhtml\">\\n`;\n\n for (const entry of finalEntries) {\n // 🔥 ÉTAPE CRUCIALE v1.1.9 : Validation croisée pré-génération\n validateCrossFields(entry);\n\n xml += ` <url>\\n`;\n \n // 1. Éléments de base et hreflang alternatifs\n xml += buildUrlBaseXml(entry, options, now);\n\n // 2. Extension Images Google\n xml += buildImageXml(entry.images);\n\n // 3. Extension Vidéos Google (Validations v1.1.3 & v1.1.4 intégrées)\n xml += buildVideoXml(entry.videos);\n\n // 4. Extension News Google\n xml += buildNewsXml(entry.news);\n\n xml += ` </url>\\n`;\n }\n\n xml += `</urlset>`;\n return xml;\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapIndexEntry } from '../../types/sitemap.js';\nimport { escapeXml } from '../../utils/xml-escape.js';\nimport { sanitizeAndValidateUrl } from './url-builder.js';\n\n/**\n * Génère la structure brute XML pour un fichier d'indexation de sitemaps.\n */\nexport function buildSitemapIndexXml(entries: SitemapIndexEntry[]): 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 entry of entries) {\n const cleanLoc = sanitizeAndValidateUrl(entry.loc, 'sitemap index location');\n \n xml += ` <sitemap>\\n`;\n xml += ` <loc>${escapeXml(cleanLoc)}</loc>\\n`;\n \n if (entry.lastmod) {\n const date = entry.lastmod instanceof Date ? entry.lastmod.toISOString() : entry.lastmod;\n xml += ` <lastmod>${date}</lastmod>\\n`;\n }\n \n xml += ` </sitemap>\\n`;\n }\n\n xml += `</sitemapindex>`;\n return xml;\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry, SitemapOptions, SitemapIndexEntry } from './types/sitemap.js';\nimport { generateXml } from './core/generator.js';\nimport { buildSitemapIndexXml } from './core/builders/index-builder.js';\n\nexport * from './types/sitemap.js';\n\n/**\n * Génère une réponse HTTP compatible Next.js (App Router) avec options de configuration.\n * v1.0.9 : Injection dynamique et personnalisable de l'en-tête Cache-Control via l'option maxAge.\n * \n * @param entries - Liste des entrées du sitemap\n * @param options - Options de génération et de mise en cache (ex: autoLastmod, maxAge)\n * @returns Une instance de Response contenant le flux XML configuré\n */\nexport function getServerSitemapResponse(\n entries: SitemapEntry[], \n options: SitemapOptions = {}\n): Response {\n const xml = generateXml(entries, options);\n\n // Détermination de la stratégie de mise en cache (v1.0.9)\n const cacheControlHeader = options.maxAge !== undefined\n ? `public, max-age=${options.maxAge}, must-revalidate`\n : 'public, s-maxage=86400, stale-while-revalidate';\n\n return new Response(xml, {\n headers: {\n 'Content-Type': 'application/xml',\n 'Cache-Control': cacheControlHeader,\n },\n });\n}\n\n/**\n * ✨ v1.2.0 : Génère une réponse HTTP Next.js (App Router) pour un Index de Sitemaps.\n * Permet de lister et de regrouper plusieurs sous-fichiers XML sitemaps.\n * \n * @param entries - Liste des sous-sitemaps composant l'index\n * @param options - Options de configuration (ex: maxAge pour le cache)\n * @returns Une instance de Response contenant le flux XML de l'index\n */\nexport function getServerSitemapIndexResponse(\n entries: SitemapIndexEntry[],\n options: Pick<SitemapOptions, 'maxAge'> = {}\n): Response {\n const xml = buildSitemapIndexXml(entries);\n\n // Détermination de la stratégie de mise en cache (v1.2.0)\n const cacheControlHeader = options.maxAge !== undefined\n ? `public, max-age=${options.maxAge}, must-revalidate`\n : 'public, s-maxage=86400, stale-while-revalidate';\n\n return new Response(xml, {\n headers: {\n 'Content-Type': 'application/xml',\n 'Cache-Control': cacheControlHeader,\n },\n });\n}"],"mappings":";AASO,SAAS,UAAU,QAA2C;AACnE,MAAI,CAAC,OAAQ,QAAO;AAEpB,SAAO,OAAO,QAAQ,YAAY,CAAC,MAAM;AACvC,YAAQ,GAAG;AAAA,MACT,KAAK;AAAK,eAAO;AAAA,MACjB,KAAK;AAAK,eAAO;AAAA,MACjB,KAAK;AAAK,eAAO;AAAA,MACjB,KAAK;AAAK,eAAO;AAAA,MACjB,KAAK;AAAK,eAAO;AAAA,MACjB;AAAS,eAAO;AAAA,IAClB;AAAA,EACF,CAAC;AACH;;;ACVO,SAAS,uBAAuB,QAAgB,SAAyB;AAC9E,QAAM,MAAM,SAAS,OAAO,KAAK,IAAI;AAErC,MAAI,CAAC,IAAI,WAAW,SAAS,KAAK,CAAC,IAAI,WAAW,UAAU,GAAG;AAC7D,UAAM,IAAI;AAAA,MACR,0CAA0C,OAAO,MAAM,GAAG;AAAA,IAC5D;AAAA,EACF;AAEA,MAAI,IAAI,SAAS,GAAG,GAAG;AACrB,UAAM,IAAI;AAAA,MACR,+DAA+D,OAAO,MAAM,GAAG;AAAA,IACjF;AAAA,EACF;AAEA,MAAI,UAAU;AACd,MAAI,OAAO,IAAI,aAAa,YAAY;AACtC,cAAU,IAAI,SAAS,GAAG;AAAA,EAC5B,OAAO;AACL,QAAI;AACF,UAAI,IAAI,GAAG;AACX,gBAAU;AAAA,IACZ,QAAQ;AACN,gBAAU;AAAA,IACZ;AAAA,EACF;AAEA,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR,+DAA+D,OAAO,MAAM,GAAG;AAAA,IACjF;AAAA,EACF;AAEA,SAAO;AACT;AAKO,SAAS,gBAAgB,OAAqB,SAAyB,QAAwB;AACpG,MAAI,MAAM;AAEV,QAAM,eAAe,uBAAuB,MAAM,KAAK,YAAY;AACnE,SAAO,YAAY,UAAU,YAAY,CAAC;AAAA;AAE1C,MAAI,MAAM,YAAY,QAAQ;AAC5B,eAAW,OAAO,MAAM,YAAY;AAClC,YAAM,cAAc,uBAAuB,IAAI,MAAM,gBAAgB;AACrE,aAAO,6CAA6C,UAAU,IAAI,QAAQ,CAAC,WAAW,UAAU,WAAW,CAAC;AAAA;AAAA,IAC9G;AAAA,EACF;AAEA,MAAI,eAAe,MAAM;AACzB,MAAI,QAAQ,eAAe,CAAC,cAAc;AACxC,mBAAe;AAAA,EACjB;AAEA,MAAI,cAAc;AAChB,UAAM,OAAO,wBAAwB,OAAO,aAAa,YAAY,IAAI;AACzE,WAAO,gBAAgB,IAAI;AAAA;AAAA,EAC7B;AAEA,MAAI,MAAM,YAAY;AACpB,WAAO,mBAAmB,MAAM,UAAU;AAAA;AAAA,EAC5C;AAEA,MAAI,MAAM,aAAa,QAAW;AAChC,WAAO,iBAAkB,MAAM,SAAoB,QAAQ,CAAC,CAAC;AAAA;AAAA,EAC/D;AAEA,SAAO;AACT;;;AC1EO,SAAS,cAAc,QAAwC;AACpE,MAAI,CAAC,QAAQ,OAAQ,QAAO;AAE5B,MAAI,MAAM;AACV,aAAW,OAAO,QAAQ;AACxB,UAAM,cAAc,uBAAuB,IAAI,KAAK,gBAAgB;AAEpE,WAAO;AAAA;AACP,WAAO,oBAAoB,UAAU,WAAW,CAAC;AAAA;AAEjD,QAAI,IAAI,SAAS,IAAI,MAAM,KAAK,MAAM,IAAI;AACxC,aAAO,sBAAsB,UAAU,IAAI,MAAM,KAAK,CAAC,CAAC;AAAA;AAAA,IAC1D;AACA,QAAI,IAAI,WAAW,IAAI,QAAQ,KAAK,MAAM,IAAI;AAC5C,aAAO,wBAAwB,UAAU,IAAI,QAAQ,KAAK,CAAC,CAAC;AAAA;AAAA,IAC9D;AAEA,QAAI,IAAI,gBAAgB,IAAI,aAAa,KAAK,MAAM,IAAI;AACtD,aAAO,6BAA6B,UAAU,IAAI,aAAa,KAAK,CAAC,CAAC;AAAA;AAAA,IACxE;AAEA,QAAI,IAAI,SAAS;AACf,YAAM,kBAAkB,uBAAuB,IAAI,SAAS,mBAAmB;AAC/E,aAAO,wBAAwB,UAAU,eAAe,CAAC;AAAA;AAAA,IAC3D;AAEA,WAAO;AAAA;AAAA,EACT;AAEA,SAAO;AACT;;;AC9BO,SAAS,cAAc,QAAwC;AACpE,MAAI,CAAC,QAAQ,OAAQ,QAAO;AAE5B,MAAI,MAAM;AACV,aAAW,OAAO,QAAQ;AACxB,UAAM,gBAAgB,uBAAuB,IAAI,eAAe,iBAAiB;AACjF,UAAM,kBAAkB,IAAI,cAAc,uBAAuB,IAAI,aAAa,wBAAwB,IAAI;AAC9G,UAAM,iBAAiB,IAAI,aAAa,uBAAuB,IAAI,YAAY,uBAAuB,IAAI;AAE1G,WAAO;AAAA;AACP,WAAO,8BAA8B,UAAU,aAAa,CAAC;AAAA;AAC7D,WAAO,sBAAsB,UAAU,IAAI,KAAK,CAAC;AAAA;AACjD,WAAO,4BAA4B,UAAU,IAAI,WAAW,CAAC;AAAA;AAE7D,QAAI,gBAAiB,QAAO,4BAA4B,UAAU,eAAe,CAAC;AAAA;AAClF,QAAI,eAAgB,QAAO,2BAA2B,UAAU,cAAc,CAAC;AAAA;AAE/E,QAAI,IAAI,kBAAkB;AACxB,YAAM,QAAQ,IAAI,4BAA4B,OAAO,IAAI,iBAAiB,YAAY,IAAI,IAAI;AAC9F,aAAO,iCAAiC,KAAK;AAAA;AAAA,IAC/C;AAGA,QAAI,IAAI,aAAa,QAAW;AAC9B,YAAM,gBAAgB,KAAK,MAAM,IAAI,QAAQ;AAC7C,UAAI,gBAAgB,KAAK,gBAAgB,OAAO;AAC9C,cAAM,IAAI;AAAA,UACR,mDAAmD,aAAa;AAAA,QAClE;AAAA,MACF;AACA,aAAO,yBAAyB,aAAa;AAAA;AAAA,IAC/C;AAGA,QAAI,IAAI,eAAe,QAAW;AAChC,YAAM,iBAAiB,KAAK,MAAM,IAAI,UAAU;AAChD,UAAI,iBAAiB,GAAG;AACtB,cAAM,IAAI;AAAA,UACR,qDAAqD,cAAc;AAAA,QACrE;AAAA,MACF;AACA,aAAO,2BAA2B,cAAc;AAAA;AAAA,IAClD;AAEA,QAAI,IAAI,MAAM;AACZ,aAAO,qBAAqB,IAAI,IAAI;AAAA;AAAA,IACtC;AAGA,QAAI,IAAI,aAAa;AACnB,UAAI,CAAC,IAAI,YAAY,aAAa,IAAI,YAAY,UAAU,WAAW,GAAG;AACxE,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAEA,YAAM,iBAAiB,IAAI,YAAY,UAAU,IAAI,aAAW;AAC9D,cAAM,OAAO,QAAQ,KAAK,EAAE,YAAY;AACxC,YAAI,KAAK,SAAS,KAAK,KAAK,SAAS,GAAG;AACtC,gBAAM,IAAI;AAAA,YACR,+DAA+D,OAAO;AAAA,UACxE;AAAA,QACF;AACA,eAAO;AAAA,MACT,CAAC;AAED,YAAM,eAAe,eAAe,KAAK,GAAG;AAC5C,aAAO,0CAA0C,IAAI,YAAY,YAAY,KAAK,YAAY;AAAA;AAAA,IAChG;AAGA,QAAI,IAAI,UAAU;AAChB,UAAI,CAAC,IAAI,SAAS,aAAa,IAAI,SAAS,UAAU,WAAW,GAAG;AAClE,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAEA,YAAM,iBAAiB,CAAC,OAAO,UAAU,IAAI;AAC7C,iBAAW,KAAK,IAAI,SAAS,WAAW;AACtC,YAAI,CAAC,eAAe,SAAS,CAAC,GAAG;AAC/B,gBAAM,IAAI;AAAA,YACR,mDAAmD,CAAC;AAAA,UACtD;AAAA,QACF;AAAA,MACF;AAEA,YAAM,eAAe,IAAI,SAAS,UAAU,KAAK,GAAG;AACpD,aAAO,uCAAuC,IAAI,SAAS,YAAY,KAAK,YAAY;AAAA;AAAA,IAC1F;AAEA,QAAI,IAAI,0BAA0B,QAAW;AAC3C,UAAI;AAEJ,UAAI,OAAO,IAAI,0BAA0B,WAAW;AAClD,mBAAW,IAAI,wBAAwB,QAAQ;AAAA,MACjD,WAAW,IAAI,0BAA0B,SAAS,IAAI,0BAA0B,MAAM;AACpF,mBAAW,IAAI;AAAA,MACjB,OAAO;AACL,cAAM,IAAI;AAAA,UACR,qEAAqE,IAAI,qBAAqB;AAAA,QAChG;AAAA,MACF;AAEA,aAAO,sCAAsC,QAAQ;AAAA;AAAA,IACvD;AAGA,QAAI,IAAI,OAAO;AACb,YAAM,EAAE,OAAO,UAAU,KAAK,IAAI,IAAI;AAEtC,UAAI,UAAU,UAAa,QAAQ,GAAG;AACpC,cAAM,IAAI;AAAA,UACR,uDAAuD,KAAK;AAAA,QAC9D;AAAA,MACF;AAEA,YAAM,gBAAgB,WAAW,SAAS,KAAK,EAAE,YAAY,IAAI;AACjE,UAAI,cAAc,WAAW,GAAG;AAC9B,cAAM,IAAI;AAAA,UACR,4DAA4D,QAAQ;AAAA,QACtE;AAAA,MACF;AAEA,UAAI,WAAW,gCAAgC,aAAa;AAC5D,UAAI,MAAM;AACR,YAAI,SAAS,UAAU,SAAS,OAAO;AACrC,gBAAM,IAAI;AAAA,YACR,gDAAgD,IAAI;AAAA,UACtD;AAAA,QACF;AACA,oBAAY,UAAU,IAAI;AAAA,MAC5B;AACA,kBAAY,IAAI,MAAM,QAAQ,CAAC,CAAC;AAAA;AAEhC,aAAO;AAAA,IACT;AAGA,QAAI,IAAI,aAAa,QAAW;AAC9B,YAAM,gBAAgB,IAAI,SAAS,KAAK;AACxC,UAAI,CAAC,eAAe;AAClB,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AACA,UAAI,cAAc,SAAS,KAAK;AAC9B,cAAM,IAAI;AAAA,UACR,0DAA0D,cAAc,MAAM;AAAA,QAChF;AAAA,MACF;AACA,aAAO,yBAAyB,UAAU,aAAa,CAAC;AAAA;AAAA,IAC1D;AAGA,QAAI,IAAI,MAAM;AACZ,UAAI,IAAI,KAAK,SAAS,IAAI;AACxB,cAAM,IAAI;AAAA,UACR,qDAAqD,IAAI,KAAK,MAAM;AAAA,QACtE;AAAA,MACF;AAEA,iBAAW,OAAO,IAAI,MAAM;AAC1B,cAAM,WAAW,IAAI,KAAK;AAC1B,YAAI,CAAC,UAAU;AACb,gBAAM,IAAI;AAAA,YACR;AAAA,UACF;AAAA,QACF;AACA,eAAO,oBAAoB,UAAU,QAAQ,CAAC;AAAA;AAAA,MAChD;AAAA,IACF;AAEA,WAAO;AAAA;AAAA,EACT;AAEA,SAAO;AACT;;;AClLO,SAAS,aAAa,MAAoC;AAC/D,MAAI,CAAC,KAAM,QAAO;AAElB,QAAM,QAAQ,KAAK,4BAA4B,OAAO,KAAK,iBAAiB,YAAY,IAAI,KAAK;AAEjG,MAAI,MAAM;AACV,SAAO;AAAA;AACP,SAAO;AAAA;AACP,SAAO,sBAAsB,UAAU,KAAK,IAAI,CAAC;AAAA;AACjD,SAAO,0BAA0B,UAAU,KAAK,QAAQ,CAAC;AAAA;AACzD,SAAO;AAAA;AACP,SAAO,gCAAgC,KAAK;AAAA;AAC5C,SAAO,qBAAqB,UAAU,KAAK,KAAK,CAAC;AAAA;AAGjD,MAAI,KAAK,eAAe;AACtB,QAAI,CAAC,MAAM,QAAQ,KAAK,aAAa,GAAG;AACtC,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,UAAM,mBAAmB,KAAK,cAC3B,IAAI,YAAU;AACb,YAAM,cAAc,OAAO,KAAK;AAChC,UAAI,CAAC,aAAa;AAChB,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AACA,UAAI,CAAC,YAAY,SAAS,GAAG,GAAG;AAC9B,cAAM,IAAI;AAAA,UACR,yDAAyD,WAAW;AAAA,QACtE;AAAA,MACF;AACA,aAAO;AAAA,IACT,CAAC;AAEH,QAAI,iBAAiB,SAAS,GAAG;AAE/B,YAAM,gBAAgB,iBAAiB,KAAK,GAAG;AAC/C,aAAO,6BAA6B,UAAU,aAAa,CAAC;AAAA;AAAA,IAC9D;AAAA,EACF;AAEA,SAAO;AAAA;AAEP,SAAO;AACT;;;AC7CO,SAAS,oBAAoB,OAA2B;AAE7D,MAAI,MAAM,UAAU,MAAM,OAAO,SAAS,GAAG;AAC3C,eAAW,OAAO,MAAM,QAAQ;AAG9B,UAAI,IAAI,SAAS,SAAS,IAAI,aAAa,UAAa,IAAI,WAAW,GAAG;AACxE,cAAM,IAAI;AAAA,UACR,gEAAgE,MAAM,GAAG;AAAA,QAC3E;AAAA,MACF;AAGA,UAAI,IAAI,0BAA0B,SAAS,IAAI,SAAS,IAAI,MAAM,SAAS,OAAO;AAChF,cAAM,IAAI;AAAA,UACR,gEAAgE,MAAM,GAAG;AAAA,QAC3E;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,MAAI,MAAM,MAAM;AACd,UAAM,UAAU,MAAM,KAAK,4BAA4B,OACnD,MAAM,KAAK,mBACX,IAAI,KAAK,MAAM,KAAK,gBAAgB;AAExC,UAAM,MAAM,oBAAI,KAAK;AACrB,UAAM,WAAW,IAAI,QAAQ,IAAI,QAAQ,QAAQ;AACjD,UAAM,aAAa,YAAY,MAAO,KAAK,KAAK;AAGhD,QAAI,aAAa,GAAG;AAClB,YAAM,IAAI;AAAA,QACR,gEAAgE,MAAM,GAAG,qGAAqG,WAAW,QAAQ,CAAC,CAAC;AAAA,MACrM;AAAA,IACF;AAAA,EACF;AACF;;;ACjCO,SAAS,YAAY,SAAyB,UAA0B,CAAC,GAAW;AACzF,QAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,MAAI,eAAe,CAAC,GAAG,OAAO;AAE9B,MAAI,QAAQ,gBAAgB;AAC1B,iBAAa,KAAK,CAAC,GAAG,MAAM;AAC1B,YAAM,YAAY,EAAE,aAAa,SAAa,EAAE,WAAsB;AACtE,YAAM,YAAY,EAAE,aAAa,SAAa,EAAE,WAAsB;AACtE,aAAO,YAAY;AAAA,IACrB,CAAC;AAAA,EACH;AAEA,MAAI,MAAM;AAAA;AACV,SAAO;AAAA;AACP,SAAO;AAAA;AACP,SAAO;AAAA;AACP,SAAO;AAAA;AACP,SAAO;AAAA;AAEP,aAAW,SAAS,cAAc;AAEhC,wBAAoB,KAAK;AAEzB,WAAO;AAAA;AAGP,WAAO,gBAAgB,OAAO,SAAS,GAAG;AAG1C,WAAO,cAAc,MAAM,MAAM;AAGjC,WAAO,cAAc,MAAM,MAAM;AAGjC,WAAO,aAAa,MAAM,IAAI;AAE9B,WAAO;AAAA;AAAA,EACT;AAEA,SAAO;AACP,SAAO;AACT;;;AC9CO,SAAS,qBAAqB,SAAsC;AACzE,MAAI,MAAM;AAAA;AACV,SAAO;AAAA;AAEP,aAAW,SAAS,SAAS;AAC3B,UAAM,WAAW,uBAAuB,MAAM,KAAK,wBAAwB;AAE3E,WAAO;AAAA;AACP,WAAO,YAAY,UAAU,QAAQ,CAAC;AAAA;AAEtC,QAAI,MAAM,SAAS;AACjB,YAAM,OAAO,MAAM,mBAAmB,OAAO,MAAM,QAAQ,YAAY,IAAI,MAAM;AACjF,aAAO,gBAAgB,IAAI;AAAA;AAAA,IAC7B;AAEA,WAAO;AAAA;AAAA,EACT;AAEA,SAAO;AACP,SAAO;AACT;;;ACbO,SAAS,yBACd,SACA,UAA0B,CAAC,GACjB;AACV,QAAM,MAAM,YAAY,SAAS,OAAO;AAGxC,QAAM,qBAAqB,QAAQ,WAAW,SAC1C,mBAAmB,QAAQ,MAAM,sBACjC;AAEJ,SAAO,IAAI,SAAS,KAAK;AAAA,IACvB,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,iBAAiB;AAAA,IACnB;AAAA,EACF,CAAC;AACH;AAUO,SAAS,8BACd,SACA,UAA0C,CAAC,GACjC;AACV,QAAM,MAAM,qBAAqB,OAAO;AAGxC,QAAM,qBAAqB,QAAQ,WAAW,SAC1C,mBAAmB,QAAQ,MAAM,sBACjC;AAEJ,SAAO,IAAI,SAAS,KAAK;AAAA,IACvB,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,iBAAiB;AAAA,IACnB;AAAA,EACF,CAAC;AACH;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-advanced-sitemap",
3
- "version": "1.1.9",
3
+ "version": "1.2.0",
4
4
  "type": "module",
5
5
  "description": "Advanced sitemap generator for Next.js. Powerful support for Google Images, Video, News, and Hreflang (multilingual). Type-safe and built for App Router.",
6
6
  "main": "./dist/index.cjs",