next-advanced-sitemap 1.1.9 → 1.2.2

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,16 @@
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
+ - **Universal XML Namespace Injection & Strict Index Guardrails (v1.2.2)**: Automated compliance matching that embeds standard canonical namespaces (`xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"`) inside root index configurations. Prevents parsing errors or validation dropouts across alternative search crawlers like Bing, Yandex, or DuckDuckGo while routing individual child locations through strict syntax URL engines.
15
+ - **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
16
  - **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
17
  - **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
18
  - **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 +44,9 @@ npm install next-advanced-sitemap
42
44
 
43
45
  ## Usage
44
46
 
45
- To implement an advanced sitemap in the Next.js App Router, create a Route Handler at `app/sitemap.xml/route.ts`.
47
+ ### 1. Generating a Sub-Sitemap (Standard XML)
48
+
49
+ To implement a rich structural sitemap in the Next.js App Router, create a Route Handler at `app/sitemap-records.xml/route.ts`.
46
50
 
47
51
  ```typescript
48
52
  import { getServerSitemapResponse, SitemapEntry } from 'next-advanced-sitemap';
@@ -70,22 +74,13 @@ export async function GET() {
70
74
  publication_date: new Date(),
71
75
  duration: 7200,
72
76
  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)
77
+ category: 'Education & Technology',
78
+ tags: ['nextjs', 'typescript', 'advanced seo'],
77
79
  price: { value: 19.99, currency: 'usd', type: 'own' },
78
- // v1.1.5: Flexible Paywall Registration (Accepts boolean or strict 'yes' | 'no')
79
80
  requires_subscription: true,
80
- // v1.1.4: Strict Geographic Filtering & Capitalization Sanitization
81
81
  restriction: {
82
82
  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
83
+ countries: ['cd', 'fr', 'us']
89
84
  }
90
85
  }
91
86
  ]
@@ -93,31 +88,16 @@ export async function GET() {
93
88
  {
94
89
  url: 'https://fomadev.com/news/fintech-drc-2026',
95
90
  priority: 0.85,
96
- // v1.1.8: Google News Syndication Matrix with Market Stock Tickers
97
91
  news: {
98
92
  name: 'FomaDev Insights',
99
93
  language: 'fr',
100
- publication_date: new Date(),
94
+ publication_date: new Date(), // Always dynamic to meet the strict 48h rule (v1.1.9)
101
95
  title: 'The Rise of FinTech Infrastructure in Central Africa',
102
- stock_tickers: [' NYSE:BABA ', 'NASDAQ:AAPL'] // Auto-trimmed, validated & comma-separated
96
+ stock_tickers: ['NYSE:BABA', 'NASDAQ:AAPL']
103
97
  }
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
98
  }
118
99
  ];
119
100
 
120
- // Enable autoLastmod and sortByPriority (v1.0.8) to optimize crawl efficiency
121
101
  return getServerSitemapResponse(entries, {
122
102
  autoLastmod: true,
123
103
  sortByPriority: true
@@ -125,19 +105,49 @@ export async function GET() {
125
105
  }
126
106
  ```
127
107
 
108
+ ### 2. Generating a Master Sitemap Index (v1.2.0)
109
+
110
+ When scaling up your application, group multiple sub-sitemaps dynamically. Create a Route Handler at `app/sitemap.xml/route.ts`.
111
+
112
+ ```typescript
113
+ import { getServerSitemapIndexResponse, SitemapIndexEntry } from 'next-advanced-sitemap';
114
+
115
+ export async function GET() {
116
+ const subSitemaps: SitemapIndexEntry[] = [
117
+ {
118
+ loc: 'https://fomadev.com/sitemap-records.xml',
119
+ lastmod: new Date() // Supports native JavaScript Date polymorphism
120
+ },
121
+ {
122
+ loc: 'https://fomadev.com/sitemap-products.xml',
123
+ lastmod: '2026-07-04T12:00:00.000Z'
124
+ }
125
+ ];
126
+
127
+ // Serves a structural <sitemapindex> with optimal CDN caching headers
128
+ return getServerSitemapIndexResponse(subSitemaps, {
129
+ maxAge: 3600 // Custom cache eviction lifespan (optional)
130
+ });
131
+ }
132
+ ```
133
+
128
134
  ## API Reference
129
135
 
136
+ ### getServerSitemapIndexResponse(entries: SitemapIndexEntry[], options?: Pick<SitemapOptions, 'maxAge'>)
137
+
138
+ **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.
139
+
130
140
  ### getServerSitemapResponse(entries: SitemapEntry[], options?: SitemapOptions)
131
141
 
132
142
  Generates a standard Next.js `Response` object with the correct `application/xml` content-type and optimized cache headers.
133
143
 
134
- ### Options:
144
+ ### Options Matrix:
135
145
 
136
- * `autoLastmod` (boolean): If `true`, injects the current ISO date for any entry missing the `lastmod` property.
146
+ * `autoLastmod` (boolean): If `true`, injects the current ISO date for any standard entry missing the `lastmod` property.
137
147
 
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`.
148
+ * `sortByPriority` (boolean): If `true`, sorts standard records in a descending sequence based on priority level (`1.0` down to `0.0`).
139
149
 
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.
150
+ * `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
151
 
142
152
  ### SitemapEntry Object
143
153
 
@@ -328,6 +338,19 @@ Generates a standard Next.js `Response` object with the correct `application/xml
328
338
 
329
339
  ## Technical Implementation
330
340
 
341
+ ### Explicit Index Namespace Ingestion & Cross-Engine Interoperability (v1.2.2)
342
+ To secure discovery velocity across alternative crawlers (e.g., Bingbot) that reject unmapped root metadata structures, **v1.2.2** enforces strict compliance standards onto index generation trees:
343
+
344
+ * **Authoritative Schema Delivery**: The generator automatically injects the structural `xmlns` URI schema into the core `<sitemapindex>` element. This prevents alternative parsers from treating the output as an unindexed plain document.
345
+ * **Unified Normalization Layer**: Consolidates fault-tolerant property fallback mapping (recovering from `.url` variations seamlessly) and applies rigid validation patterns to intercept faulty localizations prior to production rendering.
346
+
347
+ ### Native Sitemap Indexing Architecture & Edge Cache Alignment (v1.2.0)
348
+ To comfortably scale applications past search engine structural thresholds (max 50,000 URLs or 50MB per single uncompressed file), **v1.2.0** introduces a high-performance orchestration layer dedicated to nested sitemap index tree structures (`<sitemapindex>`):
349
+
350
+ * **Isolated Composition Engine**: The index builder avoids loading heavy polymorphic page matrices into memory. Instead, it relies on an ultra-lightweight serialization pipeline (`buildSitemapIndexXml`) dedicated exclusively to mapping nested `.xml` target links.
351
+ * **Shared Platform-Level Security**: Rather than implementing loose string references, the location processor (`loc`) is strictly routed through the core platform URL verification matrix. This intercepts structural mistakes, protocol anomalies (e.g. `ftp://`), and unencoded spaces before emitting a broken index payload.
352
+ * **Unified CDN Distribution Controls**: Both standard sitemaps and index structures share identical cache configuration capabilities. By default, index handlers emit immutable Edge CDN optimization directives (`public, s-maxage=86400, stale-while-revalidate`), while seamlessly unlocking manual invalidation windows via selective type extraction (`Pick<SitemapOptions, 'maxAge'>`).
353
+
331
354
  ### Cross-Field Semantic Validation & Search Console Guarantees (v1.1.9)
332
355
  To enforce an absolute 100% SEO health score and completely prevent index drops caused by structural logic contradictions, **v1.1.9** introduces an isolated pre-generation validation layer (`validateCrossFields`). The core engine scans entry matrices and enforces strict cross-field business rules:
333
356
 
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);
@@ -408,13 +409,17 @@ function generateXml(entries, options = {}) {
408
409
  xml += ` xmlns:xhtml="http://www.w3.org/1999/xhtml">
409
410
  `;
410
411
  for (const entry of finalEntries) {
411
- validateCrossFields(entry);
412
+ const normalizedEntry = { ...entry };
413
+ if (entry.video && !normalizedEntry.videos) {
414
+ normalizedEntry.videos = [entry.video];
415
+ }
416
+ validateCrossFields(normalizedEntry);
412
417
  xml += ` <url>
413
418
  `;
414
- xml += buildUrlBaseXml(entry, options, now);
415
- xml += buildImageXml(entry.images);
416
- xml += buildVideoXml(entry.videos);
417
- xml += buildNewsXml(entry.news);
419
+ xml += buildUrlBaseXml(normalizedEntry, options, now);
420
+ xml += buildImageXml(normalizedEntry.images);
421
+ xml += buildVideoXml(normalizedEntry.videos);
422
+ xml += buildNewsXml(normalizedEntry.news);
418
423
  xml += ` </url>
419
424
  `;
420
425
  }
@@ -422,6 +427,31 @@ function generateXml(entries, options = {}) {
422
427
  return xml;
423
428
  }
424
429
 
430
+ // src/core/builders/index-builder.ts
431
+ function buildSitemapIndexXml(entries) {
432
+ let xml = `<?xml version="1.0" encoding="UTF-8"?>
433
+ `;
434
+ xml += `<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
435
+ `;
436
+ for (const entry of entries) {
437
+ const targetLoc = entry.loc || entry.url || "";
438
+ const cleanLoc = sanitizeAndValidateUrl(targetLoc, "sitemap index location");
439
+ xml += ` <sitemap>
440
+ `;
441
+ xml += ` <loc>${escapeXml(cleanLoc)}</loc>
442
+ `;
443
+ if (entry.lastmod) {
444
+ const date = entry.lastmod instanceof Date ? entry.lastmod.toISOString() : entry.lastmod;
445
+ xml += ` <lastmod>${date}</lastmod>
446
+ `;
447
+ }
448
+ xml += ` </sitemap>
449
+ `;
450
+ }
451
+ xml += `</sitemapindex>`;
452
+ return xml;
453
+ }
454
+
425
455
  // src/index.ts
426
456
  function getServerSitemapResponse(entries, options = {}) {
427
457
  const xml = generateXml(entries, options);
@@ -433,8 +463,19 @@ function getServerSitemapResponse(entries, options = {}) {
433
463
  }
434
464
  });
435
465
  }
466
+ function getServerSitemapIndexResponse(entries, options = {}) {
467
+ const xml = buildSitemapIndexXml(entries);
468
+ const cacheControlHeader = options.maxAge !== void 0 ? `public, max-age=${options.maxAge}, must-revalidate` : "public, s-maxage=86400, stale-while-revalidate";
469
+ return new Response(xml, {
470
+ headers: {
471
+ "Content-Type": "application/xml",
472
+ "Cache-Control": cacheControlHeader
473
+ }
474
+ });
475
+ }
436
476
  // Annotate the CommonJS export names for ESM import in node:
437
477
  0 && (module.exports = {
478
+ getServerSitemapIndexResponse,
438
479
  getServerSitemapResponse
439
480
  });
440
481
  //# 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.2.1 : Normalisation de l'arborescence d'entrée (video/videos) pour la validation croisée négative.\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 // 💡 Normalisation préventive : Si l'utilisateur fournit \"video\" au lieu de \"videos\",\n // on s'assure que le tableau \"videos\" est peuplé pour l'analyse et la génération.\n const normalizedEntry = { ...entry };\n \n if ((entry as any).video && !normalizedEntry.videos) {\n normalizedEntry.videos = [((entry as any).video)];\n }\n\n // 🔥 ÉTAPE CRUCIALE v1.1.9 / v1.2.1 : Validation croisée pré-génération sur l'entrée normalisée\n validateCrossFields(normalizedEntry);\n\n xml += ` <url>\\n`;\n \n // 1. Éléments de base et hreflang alternatifs\n xml += buildUrlBaseXml(normalizedEntry, options, now);\n\n // 2. Extension Images Google\n xml += buildImageXml(normalizedEntry.images);\n\n // 3. Extension Vidéos Google (Validations v1.1.3 & v1.1.4 intégrées)\n xml += buildVideoXml(normalizedEntry.videos);\n\n // 4. Extension News Google\n xml += buildNewsXml(normalizedEntry.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 * v1.2.2 : Injection automatique de l'espace de nommage (xmlns) valide et validation syntaxique stricte.\n */\nexport function buildSitemapIndexXml(entries: SitemapIndexEntry[]): string {\n let xml = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n`;\n // v1.2.2 : Namespace officiel universel pour sitemapindex (valide pour Google, Bing, DuckDuckGo)\n xml += `<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\\n`;\n\n for (const entry of entries) {\n // Normalisation préventive\n const targetLoc = entry.loc || (entry as any).url || '';\n \n // Validation syntaxique stricte v1.0.4 appliquée aux index (v1.2.1)\n const cleanLoc = sanitizeAndValidateUrl(targetLoc, '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":"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","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
@@ -382,13 +382,17 @@ function generateXml(entries, options = {}) {
382
382
  xml += ` xmlns:xhtml="http://www.w3.org/1999/xhtml">
383
383
  `;
384
384
  for (const entry of finalEntries) {
385
- validateCrossFields(entry);
385
+ const normalizedEntry = { ...entry };
386
+ if (entry.video && !normalizedEntry.videos) {
387
+ normalizedEntry.videos = [entry.video];
388
+ }
389
+ validateCrossFields(normalizedEntry);
386
390
  xml += ` <url>
387
391
  `;
388
- xml += buildUrlBaseXml(entry, options, now);
389
- xml += buildImageXml(entry.images);
390
- xml += buildVideoXml(entry.videos);
391
- xml += buildNewsXml(entry.news);
392
+ xml += buildUrlBaseXml(normalizedEntry, options, now);
393
+ xml += buildImageXml(normalizedEntry.images);
394
+ xml += buildVideoXml(normalizedEntry.videos);
395
+ xml += buildNewsXml(normalizedEntry.news);
392
396
  xml += ` </url>
393
397
  `;
394
398
  }
@@ -396,6 +400,31 @@ function generateXml(entries, options = {}) {
396
400
  return xml;
397
401
  }
398
402
 
403
+ // src/core/builders/index-builder.ts
404
+ function buildSitemapIndexXml(entries) {
405
+ let xml = `<?xml version="1.0" encoding="UTF-8"?>
406
+ `;
407
+ xml += `<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
408
+ `;
409
+ for (const entry of entries) {
410
+ const targetLoc = entry.loc || entry.url || "";
411
+ const cleanLoc = sanitizeAndValidateUrl(targetLoc, "sitemap index location");
412
+ xml += ` <sitemap>
413
+ `;
414
+ xml += ` <loc>${escapeXml(cleanLoc)}</loc>
415
+ `;
416
+ if (entry.lastmod) {
417
+ const date = entry.lastmod instanceof Date ? entry.lastmod.toISOString() : entry.lastmod;
418
+ xml += ` <lastmod>${date}</lastmod>
419
+ `;
420
+ }
421
+ xml += ` </sitemap>
422
+ `;
423
+ }
424
+ xml += `</sitemapindex>`;
425
+ return xml;
426
+ }
427
+
399
428
  // src/index.ts
400
429
  function getServerSitemapResponse(entries, options = {}) {
401
430
  const xml = generateXml(entries, options);
@@ -407,7 +436,18 @@ function getServerSitemapResponse(entries, options = {}) {
407
436
  }
408
437
  });
409
438
  }
439
+ function getServerSitemapIndexResponse(entries, options = {}) {
440
+ const xml = buildSitemapIndexXml(entries);
441
+ const cacheControlHeader = options.maxAge !== void 0 ? `public, max-age=${options.maxAge}, must-revalidate` : "public, s-maxage=86400, stale-while-revalidate";
442
+ return new Response(xml, {
443
+ headers: {
444
+ "Content-Type": "application/xml",
445
+ "Cache-Control": cacheControlHeader
446
+ }
447
+ });
448
+ }
410
449
  export {
450
+ getServerSitemapIndexResponse,
411
451
  getServerSitemapResponse
412
452
  };
413
453
  //# 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.2.1 : Normalisation de l'arborescence d'entrée (video/videos) pour la validation croisée négative.\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 // 💡 Normalisation préventive : Si l'utilisateur fournit \"video\" au lieu de \"videos\",\n // on s'assure que le tableau \"videos\" est peuplé pour l'analyse et la génération.\n const normalizedEntry = { ...entry };\n \n if ((entry as any).video && !normalizedEntry.videos) {\n normalizedEntry.videos = [((entry as any).video)];\n }\n\n // 🔥 ÉTAPE CRUCIALE v1.1.9 / v1.2.1 : Validation croisée pré-génération sur l'entrée normalisée\n validateCrossFields(normalizedEntry);\n\n xml += ` <url>\\n`;\n \n // 1. Éléments de base et hreflang alternatifs\n xml += buildUrlBaseXml(normalizedEntry, options, now);\n\n // 2. Extension Images Google\n xml += buildImageXml(normalizedEntry.images);\n\n // 3. Extension Vidéos Google (Validations v1.1.3 & v1.1.4 intégrées)\n xml += buildVideoXml(normalizedEntry.videos);\n\n // 4. Extension News Google\n xml += buildNewsXml(normalizedEntry.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 * v1.2.2 : Injection automatique de l'espace de nommage (xmlns) valide et validation syntaxique stricte.\n */\nexport function buildSitemapIndexXml(entries: SitemapIndexEntry[]): string {\n let xml = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n`;\n // v1.2.2 : Namespace officiel universel pour sitemapindex (valide pour Google, Bing, DuckDuckGo)\n xml += `<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\\n`;\n\n for (const entry of entries) {\n // Normalisation préventive\n const targetLoc = entry.loc || (entry as any).url || '';\n \n // Validation syntaxique stricte v1.0.4 appliquée aux index (v1.2.1)\n const cleanLoc = sanitizeAndValidateUrl(targetLoc, '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":"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","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.2",
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",