next-advanced-sitemap 1.2.0 → 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 +14 -0
- package/dist/index.cjs +11 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +11 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,6 +11,7 @@ While Next.js provides a built-in `MetadataRoute.Sitemap` utility, it currently
|
|
|
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.
|
|
14
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.
|
|
15
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.
|
|
16
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.
|
|
@@ -337,6 +338,19 @@ Generates a standard Next.js `Response` object with the correct `application/xml
|
|
|
337
338
|
|
|
338
339
|
## Technical Implementation
|
|
339
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
|
+
|
|
340
354
|
### Cross-Field Semantic Validation & Search Console Guarantees (v1.1.9)
|
|
341
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:
|
|
342
356
|
|
package/dist/index.cjs
CHANGED
|
@@ -409,13 +409,17 @@ function generateXml(entries, options = {}) {
|
|
|
409
409
|
xml += ` xmlns:xhtml="http://www.w3.org/1999/xhtml">
|
|
410
410
|
`;
|
|
411
411
|
for (const entry of finalEntries) {
|
|
412
|
-
|
|
412
|
+
const normalizedEntry = { ...entry };
|
|
413
|
+
if (entry.video && !normalizedEntry.videos) {
|
|
414
|
+
normalizedEntry.videos = [entry.video];
|
|
415
|
+
}
|
|
416
|
+
validateCrossFields(normalizedEntry);
|
|
413
417
|
xml += ` <url>
|
|
414
418
|
`;
|
|
415
|
-
xml += buildUrlBaseXml(
|
|
416
|
-
xml += buildImageXml(
|
|
417
|
-
xml += buildVideoXml(
|
|
418
|
-
xml += buildNewsXml(
|
|
419
|
+
xml += buildUrlBaseXml(normalizedEntry, options, now);
|
|
420
|
+
xml += buildImageXml(normalizedEntry.images);
|
|
421
|
+
xml += buildVideoXml(normalizedEntry.videos);
|
|
422
|
+
xml += buildNewsXml(normalizedEntry.news);
|
|
419
423
|
xml += ` </url>
|
|
420
424
|
`;
|
|
421
425
|
}
|
|
@@ -430,7 +434,8 @@ function buildSitemapIndexXml(entries) {
|
|
|
430
434
|
xml += `<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
431
435
|
`;
|
|
432
436
|
for (const entry of entries) {
|
|
433
|
-
const
|
|
437
|
+
const targetLoc = entry.loc || entry.url || "";
|
|
438
|
+
const cleanLoc = sanitizeAndValidateUrl(targetLoc, "sitemap index location");
|
|
434
439
|
xml += ` <sitemap>
|
|
435
440
|
`;
|
|
436
441
|
xml += ` <loc>${escapeXml(cleanLoc)}</loc>
|
package/dist/index.cjs.map
CHANGED
|
@@ -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","../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 '<';\n case '>': return '>';\n case '&': return '&';\n case '\"': return '"';\n case \"'\": return ''';\n default: return c;\n }\n });\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry, SitemapOptions } from '../../types/sitemap.js';\nimport { escapeXml } from '../../utils/xml-escape.js';\n\n/**\n * Nettoie et valide de manière stricte le format et la structure d'une URL.\n * v1.0.7 : Intégration de l'Auto-Trimming (nettoyage des espaces de début et de fin)\n */\nexport function sanitizeAndValidateUrl(rawUrl: string, context: string): string {\n const url = rawUrl ? rawUrl.trim() : '';\n\n if (!url.startsWith('http://') && !url.startsWith('https://')) {\n throw new Error(\n `[next-advanced-sitemap] Invalid URL in ${context}: \"${url}\". URLs must start with http:// or https://`\n );\n }\n\n if (url.includes(' ')) {\n throw new Error(\n `[next-advanced-sitemap] Malformed URL structure detected in ${context}: \"${url}\". Please verify spaces or special characters.`\n );\n }\n\n let isValid = false;\n if (typeof URL.canParse === 'function') {\n isValid = URL.canParse(url);\n } else {\n try {\n new URL(url);\n isValid = true;\n } catch {\n isValid = false;\n }\n }\n\n if (!isValid) {\n throw new Error(\n `[next-advanced-sitemap] Malformed URL structure detected in ${context}: \"${url}\". Please verify spaces or special characters.`\n );\n }\n\n return url;\n}\n\n/**\n * Génère le bloc XML de base pour un nœud URL (loc, alternates, lastmod, changefreq, priority).\n */\nexport function buildUrlBaseXml(entry: SitemapEntry, options: SitemapOptions, nowIso: string): string {\n let xml = '';\n \n const cleanMainUrl = sanitizeAndValidateUrl(entry.url, 'main entry');\n xml += ` <loc>${escapeXml(cleanMainUrl)}</loc>\\n`;\n\n if (entry.alternates?.length) {\n for (const alt of entry.alternates) {\n const cleanAltUrl = sanitizeAndValidateUrl(alt.href, 'alternate link');\n xml += ` <xhtml:link rel=\"alternate\" hreflang=\"${escapeXml(alt.hreflang)}\" href=\"${escapeXml(cleanAltUrl)}\" />\\n`;\n }\n }\n\n let lastmodValue = entry.lastmod;\n if (options.autoLastmod && !lastmodValue) {\n lastmodValue = nowIso;\n }\n\n if (lastmodValue) {\n const date = lastmodValue instanceof Date ? lastmodValue.toISOString() : lastmodValue;\n xml += ` <lastmod>${date}</lastmod>\\n`;\n }\n\n if (entry.changefreq) {\n xml += ` <changefreq>${entry.changefreq}</changefreq>\\n`;\n }\n\n if (entry.priority !== undefined) {\n xml += ` <priority>${(entry.priority as number).toFixed(1)}</priority>\\n`;\n }\n\n return xml;\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry } from '../../types/sitemap.js';\nimport { escapeXml } from '../../utils/xml-escape.js';\nimport { sanitizeAndValidateUrl } from './url-builder.js';\n\nexport function buildImageXml(images: SitemapEntry['images']): string {\n if (!images?.length) return '';\n\n let xml = '';\n for (const img of images) {\n const cleanImgUrl = sanitizeAndValidateUrl(img.loc, 'image location');\n \n xml += ` <image:image>\\n`;\n xml += ` <image:loc>${escapeXml(cleanImgUrl)}</image:loc>\\n`;\n \n if (img.title && img.title.trim() !== '') {\n xml += ` <image:title>${escapeXml(img.title.trim())}</image:title>\\n`;\n }\n if (img.caption && img.caption.trim() !== '') {\n xml += ` <image:caption>${escapeXml(img.caption.trim())}</image:caption>\\n`;\n }\n \n if (img.geo_location && img.geo_location.trim() !== '') {\n xml += ` <image:geo_location>${escapeXml(img.geo_location.trim())}</image:geo_location>\\n`;\n }\n \n if (img.license) {\n const cleanLicenseUrl = sanitizeAndValidateUrl(img.license, 'image license URL');\n xml += ` <image:license>${escapeXml(cleanLicenseUrl)}</image:license>\\n`;\n }\n \n xml += ` </image:image>\\n`;\n }\n \n return xml;\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry } from '../../types/sitemap.js';\nimport { escapeXml } from '../../utils/xml-escape.js';\nimport { sanitizeAndValidateUrl } from './url-builder.js';\n\nexport function buildVideoXml(videos: SitemapEntry['videos']): string {\n if (!videos?.length) return '';\n\n let xml = '';\n for (const vid of videos) {\n const cleanThumbLoc = sanitizeAndValidateUrl(vid.thumbnail_loc, 'video thumbnail');\n const cleanContentLoc = vid.content_loc ? sanitizeAndValidateUrl(vid.content_loc, 'video content location') : undefined;\n const cleanPlayerLoc = vid.player_loc ? sanitizeAndValidateUrl(vid.player_loc, 'video player location') : undefined;\n\n xml += ` <video:video>\\n`;\n xml += ` <video:thumbnail_loc>${escapeXml(cleanThumbLoc)}</video:thumbnail_loc>\\n`;\n xml += ` <video:title>${escapeXml(vid.title)}</video:title>\\n`;\n xml += ` <video:description>${escapeXml(vid.description)}</video:description>\\n`;\n \n if (cleanContentLoc) xml += ` <video:content_loc>${escapeXml(cleanContentLoc)}</video:content_loc>\\n`;\n if (cleanPlayerLoc) xml += ` <video:player_loc>${escapeXml(cleanPlayerLoc)}</video:player_loc>\\n`;\n \n if (vid.publication_date) {\n const vDate = vid.publication_date instanceof Date ? vid.publication_date.toISOString() : vid.publication_date;\n xml += ` <video:publication_date>${vDate}</video:publication_date>\\n`;\n }\n\n // ✨ Validation et Sérialisation de la durée (0 - 28800s)\n if (vid.duration !== undefined) {\n const finalDuration = Math.floor(vid.duration);\n if (finalDuration < 0 || finalDuration > 28800) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video duration: ${finalDuration}. Duration must be an integer between 0 and 28800 seconds (8 hours).`\n );\n }\n xml += ` <video:duration>${finalDuration}</video:duration>\\n`;\n }\n\n // ✨ Validation et Sérialisation du nombre de vues (>= 0)\n if (vid.view_count !== undefined) {\n const finalViewCount = Math.floor(vid.view_count);\n if (finalViewCount < 0) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video view_count: ${finalViewCount}. View count cannot be negative.`\n );\n }\n xml += ` <video:view_count>${finalViewCount}</video:view_count>\\n`;\n }\n\n if (vid.live) {\n xml += ` <video:live>${vid.live}</video:live>\\n`;\n }\n\n // ✨ Validation et Sérialisation des Restrictions Pays (v1.1.4)\n if (vid.restriction) {\n if (!vid.restriction.countries || vid.restriction.countries.length === 0) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video restriction: countries array cannot be empty.`\n );\n }\n\n const cleanCountries = vid.restriction.countries.map(country => {\n const code = country.trim().toUpperCase();\n if (code.length < 2 || code.length > 3) {\n throw new Error(\n `[next-advanced-sitemap] Invalid ISO country code detected: \"${country}\". Must be a valid ISO 3166 code.`\n );\n }\n return code;\n });\n\n const countriesStr = cleanCountries.join(' ');\n xml += ` <video:restriction relationship=\"${vid.restriction.relationship}\">${countriesStr}</video:restriction>\\n`;\n }\n\n // ✨ Validation et Sérialisation des Plateformes (v1.1.4)\n if (vid.platform) {\n if (!vid.platform.platforms || vid.platform.platforms.length === 0) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video platform: platforms array cannot be empty.`\n );\n }\n\n const validPlatforms = ['web', 'mobile', 'tv'];\n for (const p of vid.platform.platforms) {\n if (!validPlatforms.includes(p)) {\n throw new Error(\n `[next-advanced-sitemap] Invalid platform type: \"${p}\". Allowed values are 'web', 'mobile', or 'tv'.`\n );\n }\n }\n\n const platformsStr = vid.platform.platforms.join(' ');\n xml += ` <video:platform relationship=\"${vid.platform.relationship}\">${platformsStr}</video:platform>\\n`;\n }\n \n if (vid.requires_subscription !== undefined) {\n let subValue: 'yes' | 'no';\n\n if (typeof vid.requires_subscription === 'boolean') {\n subValue = vid.requires_subscription ? 'yes' : 'no';\n } else if (vid.requires_subscription === 'yes' || vid.requires_subscription === 'no') {\n subValue = vid.requires_subscription;\n } else {\n throw new Error(\n `[next-advanced-sitemap] Invalid value for requires_subscription: \"${vid.requires_subscription}\". Expected boolean or strict string 'yes' | 'no'.`\n );\n }\n\n xml += ` <video:requires_subscription>${subValue}</video:requires_subscription>\\n`;\n }\n\n // ✨ Validation et Sérialisation des Prix et Achats (v1.1.6)\n if (vid.price) {\n const { value, currency, type } = vid.price;\n\n if (value === undefined || value < 0) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video price value: \"${value}\". Value must be a positive number.`\n );\n }\n\n const cleanCurrency = currency ? currency.trim().toUpperCase() : '';\n if (cleanCurrency.length !== 3) {\n throw new Error(\n `[next-advanced-sitemap] Invalid ISO 4217 currency code: \"${currency}\". Currency must be exactly a 3-letter code.`\n );\n }\n\n let priceXml = ` <video:price currency=\"${cleanCurrency}\"`;\n if (type) {\n if (type !== 'rent' && type !== 'own') {\n throw new Error(\n `[next-advanced-sitemap] Invalid price type: \"${type}\". Allowed values are 'rent' or 'own'.`\n );\n }\n priceXml += ` type=\"${type}\"`;\n }\n priceXml += `>${value.toFixed(2)}</video:price>\\n`;\n \n xml += priceXml;\n }\n\n // ✨ Validation et Sérialisation de la Catégorie (v1.1.7)\n if (vid.category !== undefined) {\n const cleanCategory = vid.category.trim();\n if (!cleanCategory) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video category: category cannot be empty or just whitespaces.`\n );\n }\n if (cleanCategory.length > 256) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video category length: ${cleanCategory.length}. Maximum allowed is 256 characters.`\n );\n }\n xml += ` <video:category>${escapeXml(cleanCategory)}</video:category>\\n`;\n }\n\n // ✨ Validation et Sérialisation des Tags (v1.1.7)\n if (vid.tags) {\n if (vid.tags.length > 32) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video tags count: ${vid.tags.length}. A video can have a maximum of 32 tags.`\n );\n }\n\n for (const tag of vid.tags) {\n const cleanTag = tag.trim();\n if (!cleanTag) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video tag detected: tag cannot be empty or just whitespaces.`\n );\n }\n xml += ` <video:tag>${escapeXml(cleanTag)}</video:tag>\\n`;\n }\n }\n\n xml += ` </video:video>\\n`;\n }\n \n return xml;\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry } from '../../types/sitemap.js';\nimport { escapeXml } from '../../utils/xml-escape.js';\n\nexport function buildNewsXml(news: SitemapEntry['news']): string {\n if (!news) return '';\n\n const nDate = news.publication_date instanceof Date ? news.publication_date.toISOString() : news.publication_date;\n \n let xml = '';\n xml += ` <news:news>\\n`;\n xml += ` <news:publication>\\n`;\n xml += ` <news:name>${escapeXml(news.name)}</news:name>\\n`;\n xml += ` <news:language>${escapeXml(news.language)}</news:language>\\n`;\n xml += ` </news:publication>\\n`;\n xml += ` <news:publication_date>${nDate}</news:publication_date>\\n`;\n xml += ` <news:title>${escapeXml(news.title)}</news:title>\\n`;\n\n // ✨ Validation et Sérialisation des Stock Tickers (v1.1.8)\n if (news.stock_tickers) {\n if (!Array.isArray(news.stock_tickers)) {\n throw new Error(\n `[next-advanced-sitemap] Invalid news stock_tickers: property must be an array of strings.`\n );\n }\n\n const validatedTickers = news.stock_tickers\n .map(ticker => {\n const cleanTicker = ticker.trim();\n if (!cleanTicker) {\n throw new Error(\n `[next-advanced-sitemap] Invalid stock ticker detected: ticker element cannot be empty or just whitespaces.`\n );\n }\n if (!cleanTicker.includes(':')) {\n throw new Error(\n `[next-advanced-sitemap] Invalid stock ticker format: \"${cleanTicker}\". Expected \"EXCHANGE:TICKER\" format (e.g., \"NASDAQ:AAPL\").`\n );\n }\n return cleanTicker;\n });\n\n if (validatedTickers.length > 0) {\n // Google demande une liste séparée par des virgules (sans espaces superflus)\n const tickersString = validatedTickers.join(',');\n xml += ` <news:stock_tickers>${escapeXml(tickersString)}</news:stock_tickers>\\n`;\n }\n }\n\n xml += ` </news:news>\\n`;\n \n return xml;\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry } from '../../types/sitemap.js';\n\n/**\n * Valide la cohérence logique et sémantique croisée entre les différents champs d'une entrée.\n * Garantit un score SEO Search Console parfait.\n */\nexport function validateCrossFields(entry: SitemapEntry): void {\n // 1. Validations croisées sur l'extension Vidéo\n if (entry.videos && entry.videos.length > 0) {\n for (const vid of entry.videos) {\n \n // RÈGLE A : Conflit d'accès payant vs abonnement\n if (vid.live === 'yes' && vid.duration !== undefined && vid.duration > 0) {\n throw new Error(\n `[next-advanced-sitemap] Cross-field validation error on URL \"${entry.url}\": A live video stream cannot have a pre-defined static duration.`\n );\n }\n\n // RÈGLE B : Conflit d'abonnement requis vs Prix d'achat direct sans logique transactionnelle définie\n if (vid.requires_subscription === 'yes' && vid.price && vid.price.type === 'own') {\n throw new Error(\n `[next-advanced-sitemap] Cross-field validation error on URL \"${entry.url}\": Video cannot simultaneously require a global subscription and be available for full individual ownership (\"own\").`\n );\n }\n }\n }\n\n // 2. Validations croisées sur l'extension Google News\n if (entry.news) {\n const pubDate = entry.news.publication_date instanceof Date \n ? entry.news.publication_date \n : new Date(entry.news.publication_date);\n\n const now = new Date();\n const diffInMs = now.getTime() - pubDate.getTime();\n const diffInDays = diffInMs / (1000 * 60 * 60 * 24);\n\n // RÈGLE C : Google News n'indexe pas les articles de plus de 2 jours (48 heures) via sitemap\n if (diffInDays > 2) {\n throw new Error(\n `[next-advanced-sitemap] Cross-field validation error on URL \"${entry.url}\": Google News sitemaps only support articles published within the last 48 hours. Article date is ${diffInDays.toFixed(1)} days old.`\n );\n }\n }\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry, SitemapOptions } from '../types/sitemap.js';\nimport { buildUrlBaseXml } from './builders/url-builder.js';\nimport { buildImageXml } from './builders/image-builder.js';\nimport { buildVideoXml } from './builders/video-builder.js';\nimport { buildNewsXml } from './builders/news-builder.js';\nimport { validateCrossFields } from './validation/cross-validator.js';\n\n/**\n * Génère le flux XML complet du sitemap incluant les extensions Images, Vidéos, News et Hreflang.\n * v1.1.9 : Intégration de la validation sémantique croisée en amont.\n */\nexport function generateXml(entries: SitemapEntry[], options: SitemapOptions = {}): string {\n const now = new Date().toISOString();\n let finalEntries = [...entries];\n\n if (options.sortByPriority) {\n finalEntries.sort((a, b) => {\n const priorityA = a.priority !== undefined ? (a.priority as number) : 0.5;\n const priorityB = b.priority !== undefined ? (b.priority as number) : 0.5;\n return priorityB - priorityA;\n });\n }\n \n let xml = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n`;\n xml += `<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\"\\n`;\n xml += ` xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\"\\n`;\n xml += ` xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\"\\n`;\n xml += ` xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\"\\n`;\n xml += ` xmlns:xhtml=\"http://www.w3.org/1999/xhtml\">\\n`;\n\n for (const entry of finalEntries) {\n // 🔥 ÉTAPE CRUCIALE v1.1.9 : Validation croisée pré-génération\n validateCrossFields(entry);\n\n xml += ` <url>\\n`;\n \n // 1. Éléments de base et hreflang alternatifs\n xml += buildUrlBaseXml(entry, options, now);\n\n // 2. Extension Images Google\n xml += buildImageXml(entry.images);\n\n // 3. Extension Vidéos Google (Validations v1.1.3 & v1.1.4 intégrées)\n xml += buildVideoXml(entry.videos);\n\n // 4. Extension News Google\n xml += buildNewsXml(entry.news);\n\n xml += ` </url>\\n`;\n }\n\n xml += `</urlset>`;\n return xml;\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapIndexEntry } from '../../types/sitemap.js';\nimport { escapeXml } from '../../utils/xml-escape.js';\nimport { sanitizeAndValidateUrl } from './url-builder.js';\n\n/**\n * Génère la structure brute XML pour un fichier d'indexation de sitemaps.\n */\nexport function buildSitemapIndexXml(entries: SitemapIndexEntry[]): string {\n let xml = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n`;\n xml += `<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\\n`;\n\n for (const entry of entries) {\n const cleanLoc = sanitizeAndValidateUrl(entry.loc, 'sitemap index location');\n \n xml += ` <sitemap>\\n`;\n xml += ` <loc>${escapeXml(cleanLoc)}</loc>\\n`;\n \n if (entry.lastmod) {\n const date = entry.lastmod instanceof Date ? entry.lastmod.toISOString() : entry.lastmod;\n xml += ` <lastmod>${date}</lastmod>\\n`;\n }\n \n xml += ` </sitemap>\\n`;\n }\n\n xml += `</sitemapindex>`;\n return xml;\n}"],"mappings":"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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 '<';\n case '>': return '>';\n case '&': return '&';\n case '\"': return '"';\n case \"'\": return ''';\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.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
|
-
|
|
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(
|
|
389
|
-
xml += buildImageXml(
|
|
390
|
-
xml += buildVideoXml(
|
|
391
|
-
xml += buildNewsXml(
|
|
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
|
}
|
|
@@ -403,7 +407,8 @@ function buildSitemapIndexXml(entries) {
|
|
|
403
407
|
xml += `<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
404
408
|
`;
|
|
405
409
|
for (const entry of entries) {
|
|
406
|
-
const
|
|
410
|
+
const targetLoc = entry.loc || entry.url || "";
|
|
411
|
+
const cleanLoc = sanitizeAndValidateUrl(targetLoc, "sitemap index location");
|
|
407
412
|
xml += ` <sitemap>
|
|
408
413
|
`;
|
|
409
414
|
xml += ` <loc>${escapeXml(cleanLoc)}</loc>
|
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/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 '<';\n case '>': return '>';\n case '&': return '&';\n case '\"': return '"';\n case \"'\": return ''';\n default: return c;\n }\n });\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry, SitemapOptions } from '../../types/sitemap.js';\nimport { escapeXml } from '../../utils/xml-escape.js';\n\n/**\n * Nettoie et valide de manière stricte le format et la structure d'une URL.\n * v1.0.7 : Intégration de l'Auto-Trimming (nettoyage des espaces de début et de fin)\n */\nexport function sanitizeAndValidateUrl(rawUrl: string, context: string): string {\n const url = rawUrl ? rawUrl.trim() : '';\n\n if (!url.startsWith('http://') && !url.startsWith('https://')) {\n throw new Error(\n `[next-advanced-sitemap] Invalid URL in ${context}: \"${url}\". URLs must start with http:// or https://`\n );\n }\n\n if (url.includes(' ')) {\n throw new Error(\n `[next-advanced-sitemap] Malformed URL structure detected in ${context}: \"${url}\". Please verify spaces or special characters.`\n );\n }\n\n let isValid = false;\n if (typeof URL.canParse === 'function') {\n isValid = URL.canParse(url);\n } else {\n try {\n new URL(url);\n isValid = true;\n } catch {\n isValid = false;\n }\n }\n\n if (!isValid) {\n throw new Error(\n `[next-advanced-sitemap] Malformed URL structure detected in ${context}: \"${url}\". Please verify spaces or special characters.`\n );\n }\n\n return url;\n}\n\n/**\n * Génère le bloc XML de base pour un nœud URL (loc, alternates, lastmod, changefreq, priority).\n */\nexport function buildUrlBaseXml(entry: SitemapEntry, options: SitemapOptions, nowIso: string): string {\n let xml = '';\n \n const cleanMainUrl = sanitizeAndValidateUrl(entry.url, 'main entry');\n xml += ` <loc>${escapeXml(cleanMainUrl)}</loc>\\n`;\n\n if (entry.alternates?.length) {\n for (const alt of entry.alternates) {\n const cleanAltUrl = sanitizeAndValidateUrl(alt.href, 'alternate link');\n xml += ` <xhtml:link rel=\"alternate\" hreflang=\"${escapeXml(alt.hreflang)}\" href=\"${escapeXml(cleanAltUrl)}\" />\\n`;\n }\n }\n\n let lastmodValue = entry.lastmod;\n if (options.autoLastmod && !lastmodValue) {\n lastmodValue = nowIso;\n }\n\n if (lastmodValue) {\n const date = lastmodValue instanceof Date ? lastmodValue.toISOString() : lastmodValue;\n xml += ` <lastmod>${date}</lastmod>\\n`;\n }\n\n if (entry.changefreq) {\n xml += ` <changefreq>${entry.changefreq}</changefreq>\\n`;\n }\n\n if (entry.priority !== undefined) {\n xml += ` <priority>${(entry.priority as number).toFixed(1)}</priority>\\n`;\n }\n\n return xml;\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry } from '../../types/sitemap.js';\nimport { escapeXml } from '../../utils/xml-escape.js';\nimport { sanitizeAndValidateUrl } from './url-builder.js';\n\nexport function buildImageXml(images: SitemapEntry['images']): string {\n if (!images?.length) return '';\n\n let xml = '';\n for (const img of images) {\n const cleanImgUrl = sanitizeAndValidateUrl(img.loc, 'image location');\n \n xml += ` <image:image>\\n`;\n xml += ` <image:loc>${escapeXml(cleanImgUrl)}</image:loc>\\n`;\n \n if (img.title && img.title.trim() !== '') {\n xml += ` <image:title>${escapeXml(img.title.trim())}</image:title>\\n`;\n }\n if (img.caption && img.caption.trim() !== '') {\n xml += ` <image:caption>${escapeXml(img.caption.trim())}</image:caption>\\n`;\n }\n \n if (img.geo_location && img.geo_location.trim() !== '') {\n xml += ` <image:geo_location>${escapeXml(img.geo_location.trim())}</image:geo_location>\\n`;\n }\n \n if (img.license) {\n const cleanLicenseUrl = sanitizeAndValidateUrl(img.license, 'image license URL');\n xml += ` <image:license>${escapeXml(cleanLicenseUrl)}</image:license>\\n`;\n }\n \n xml += ` </image:image>\\n`;\n }\n \n return xml;\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry } from '../../types/sitemap.js';\nimport { escapeXml } from '../../utils/xml-escape.js';\nimport { sanitizeAndValidateUrl } from './url-builder.js';\n\nexport function buildVideoXml(videos: SitemapEntry['videos']): string {\n if (!videos?.length) return '';\n\n let xml = '';\n for (const vid of videos) {\n const cleanThumbLoc = sanitizeAndValidateUrl(vid.thumbnail_loc, 'video thumbnail');\n const cleanContentLoc = vid.content_loc ? sanitizeAndValidateUrl(vid.content_loc, 'video content location') : undefined;\n const cleanPlayerLoc = vid.player_loc ? sanitizeAndValidateUrl(vid.player_loc, 'video player location') : undefined;\n\n xml += ` <video:video>\\n`;\n xml += ` <video:thumbnail_loc>${escapeXml(cleanThumbLoc)}</video:thumbnail_loc>\\n`;\n xml += ` <video:title>${escapeXml(vid.title)}</video:title>\\n`;\n xml += ` <video:description>${escapeXml(vid.description)}</video:description>\\n`;\n \n if (cleanContentLoc) xml += ` <video:content_loc>${escapeXml(cleanContentLoc)}</video:content_loc>\\n`;\n if (cleanPlayerLoc) xml += ` <video:player_loc>${escapeXml(cleanPlayerLoc)}</video:player_loc>\\n`;\n \n if (vid.publication_date) {\n const vDate = vid.publication_date instanceof Date ? vid.publication_date.toISOString() : vid.publication_date;\n xml += ` <video:publication_date>${vDate}</video:publication_date>\\n`;\n }\n\n // ✨ Validation et Sérialisation de la durée (0 - 28800s)\n if (vid.duration !== undefined) {\n const finalDuration = Math.floor(vid.duration);\n if (finalDuration < 0 || finalDuration > 28800) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video duration: ${finalDuration}. Duration must be an integer between 0 and 28800 seconds (8 hours).`\n );\n }\n xml += ` <video:duration>${finalDuration}</video:duration>\\n`;\n }\n\n // ✨ Validation et Sérialisation du nombre de vues (>= 0)\n if (vid.view_count !== undefined) {\n const finalViewCount = Math.floor(vid.view_count);\n if (finalViewCount < 0) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video view_count: ${finalViewCount}. View count cannot be negative.`\n );\n }\n xml += ` <video:view_count>${finalViewCount}</video:view_count>\\n`;\n }\n\n if (vid.live) {\n xml += ` <video:live>${vid.live}</video:live>\\n`;\n }\n\n // ✨ Validation et Sérialisation des Restrictions Pays (v1.1.4)\n if (vid.restriction) {\n if (!vid.restriction.countries || vid.restriction.countries.length === 0) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video restriction: countries array cannot be empty.`\n );\n }\n\n const cleanCountries = vid.restriction.countries.map(country => {\n const code = country.trim().toUpperCase();\n if (code.length < 2 || code.length > 3) {\n throw new Error(\n `[next-advanced-sitemap] Invalid ISO country code detected: \"${country}\". Must be a valid ISO 3166 code.`\n );\n }\n return code;\n });\n\n const countriesStr = cleanCountries.join(' ');\n xml += ` <video:restriction relationship=\"${vid.restriction.relationship}\">${countriesStr}</video:restriction>\\n`;\n }\n\n // ✨ Validation et Sérialisation des Plateformes (v1.1.4)\n if (vid.platform) {\n if (!vid.platform.platforms || vid.platform.platforms.length === 0) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video platform: platforms array cannot be empty.`\n );\n }\n\n const validPlatforms = ['web', 'mobile', 'tv'];\n for (const p of vid.platform.platforms) {\n if (!validPlatforms.includes(p)) {\n throw new Error(\n `[next-advanced-sitemap] Invalid platform type: \"${p}\". Allowed values are 'web', 'mobile', or 'tv'.`\n );\n }\n }\n\n const platformsStr = vid.platform.platforms.join(' ');\n xml += ` <video:platform relationship=\"${vid.platform.relationship}\">${platformsStr}</video:platform>\\n`;\n }\n \n if (vid.requires_subscription !== undefined) {\n let subValue: 'yes' | 'no';\n\n if (typeof vid.requires_subscription === 'boolean') {\n subValue = vid.requires_subscription ? 'yes' : 'no';\n } else if (vid.requires_subscription === 'yes' || vid.requires_subscription === 'no') {\n subValue = vid.requires_subscription;\n } else {\n throw new Error(\n `[next-advanced-sitemap] Invalid value for requires_subscription: \"${vid.requires_subscription}\". Expected boolean or strict string 'yes' | 'no'.`\n );\n }\n\n xml += ` <video:requires_subscription>${subValue}</video:requires_subscription>\\n`;\n }\n\n // ✨ Validation et Sérialisation des Prix et Achats (v1.1.6)\n if (vid.price) {\n const { value, currency, type } = vid.price;\n\n if (value === undefined || value < 0) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video price value: \"${value}\". Value must be a positive number.`\n );\n }\n\n const cleanCurrency = currency ? currency.trim().toUpperCase() : '';\n if (cleanCurrency.length !== 3) {\n throw new Error(\n `[next-advanced-sitemap] Invalid ISO 4217 currency code: \"${currency}\". Currency must be exactly a 3-letter code.`\n );\n }\n\n let priceXml = ` <video:price currency=\"${cleanCurrency}\"`;\n if (type) {\n if (type !== 'rent' && type !== 'own') {\n throw new Error(\n `[next-advanced-sitemap] Invalid price type: \"${type}\". Allowed values are 'rent' or 'own'.`\n );\n }\n priceXml += ` type=\"${type}\"`;\n }\n priceXml += `>${value.toFixed(2)}</video:price>\\n`;\n \n xml += priceXml;\n }\n\n // ✨ Validation et Sérialisation de la Catégorie (v1.1.7)\n if (vid.category !== undefined) {\n const cleanCategory = vid.category.trim();\n if (!cleanCategory) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video category: category cannot be empty or just whitespaces.`\n );\n }\n if (cleanCategory.length > 256) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video category length: ${cleanCategory.length}. Maximum allowed is 256 characters.`\n );\n }\n xml += ` <video:category>${escapeXml(cleanCategory)}</video:category>\\n`;\n }\n\n // ✨ Validation et Sérialisation des Tags (v1.1.7)\n if (vid.tags) {\n if (vid.tags.length > 32) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video tags count: ${vid.tags.length}. A video can have a maximum of 32 tags.`\n );\n }\n\n for (const tag of vid.tags) {\n const cleanTag = tag.trim();\n if (!cleanTag) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video tag detected: tag cannot be empty or just whitespaces.`\n );\n }\n xml += ` <video:tag>${escapeXml(cleanTag)}</video:tag>\\n`;\n }\n }\n\n xml += ` </video:video>\\n`;\n }\n \n return xml;\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry } from '../../types/sitemap.js';\nimport { escapeXml } from '../../utils/xml-escape.js';\n\nexport function buildNewsXml(news: SitemapEntry['news']): string {\n if (!news) return '';\n\n const nDate = news.publication_date instanceof Date ? news.publication_date.toISOString() : news.publication_date;\n \n let xml = '';\n xml += ` <news:news>\\n`;\n xml += ` <news:publication>\\n`;\n xml += ` <news:name>${escapeXml(news.name)}</news:name>\\n`;\n xml += ` <news:language>${escapeXml(news.language)}</news:language>\\n`;\n xml += ` </news:publication>\\n`;\n xml += ` <news:publication_date>${nDate}</news:publication_date>\\n`;\n xml += ` <news:title>${escapeXml(news.title)}</news:title>\\n`;\n\n // ✨ Validation et Sérialisation des Stock Tickers (v1.1.8)\n if (news.stock_tickers) {\n if (!Array.isArray(news.stock_tickers)) {\n throw new Error(\n `[next-advanced-sitemap] Invalid news stock_tickers: property must be an array of strings.`\n );\n }\n\n const validatedTickers = news.stock_tickers\n .map(ticker => {\n const cleanTicker = ticker.trim();\n if (!cleanTicker) {\n throw new Error(\n `[next-advanced-sitemap] Invalid stock ticker detected: ticker element cannot be empty or just whitespaces.`\n );\n }\n if (!cleanTicker.includes(':')) {\n throw new Error(\n `[next-advanced-sitemap] Invalid stock ticker format: \"${cleanTicker}\". Expected \"EXCHANGE:TICKER\" format (e.g., \"NASDAQ:AAPL\").`\n );\n }\n return cleanTicker;\n });\n\n if (validatedTickers.length > 0) {\n // Google demande une liste séparée par des virgules (sans espaces superflus)\n const tickersString = validatedTickers.join(',');\n xml += ` <news:stock_tickers>${escapeXml(tickersString)}</news:stock_tickers>\\n`;\n }\n }\n\n xml += ` </news:news>\\n`;\n \n return xml;\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry } from '../../types/sitemap.js';\n\n/**\n * Valide la cohérence logique et sémantique croisée entre les différents champs d'une entrée.\n * Garantit un score SEO Search Console parfait.\n */\nexport function validateCrossFields(entry: SitemapEntry): void {\n // 1. Validations croisées sur l'extension Vidéo\n if (entry.videos && entry.videos.length > 0) {\n for (const vid of entry.videos) {\n \n // RÈGLE A : Conflit d'accès payant vs abonnement\n if (vid.live === 'yes' && vid.duration !== undefined && vid.duration > 0) {\n throw new Error(\n `[next-advanced-sitemap] Cross-field validation error on URL \"${entry.url}\": A live video stream cannot have a pre-defined static duration.`\n );\n }\n\n // RÈGLE B : Conflit d'abonnement requis vs Prix d'achat direct sans logique transactionnelle définie\n if (vid.requires_subscription === 'yes' && vid.price && vid.price.type === 'own') {\n throw new Error(\n `[next-advanced-sitemap] Cross-field validation error on URL \"${entry.url}\": Video cannot simultaneously require a global subscription and be available for full individual ownership (\"own\").`\n );\n }\n }\n }\n\n // 2. Validations croisées sur l'extension Google News\n if (entry.news) {\n const pubDate = entry.news.publication_date instanceof Date \n ? entry.news.publication_date \n : new Date(entry.news.publication_date);\n\n const now = new Date();\n const diffInMs = now.getTime() - pubDate.getTime();\n const diffInDays = diffInMs / (1000 * 60 * 60 * 24);\n\n // RÈGLE C : Google News n'indexe pas les articles de plus de 2 jours (48 heures) via sitemap\n if (diffInDays > 2) {\n throw new Error(\n `[next-advanced-sitemap] Cross-field validation error on URL \"${entry.url}\": Google News sitemaps only support articles published within the last 48 hours. Article date is ${diffInDays.toFixed(1)} days old.`\n );\n }\n }\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry, SitemapOptions } from '../types/sitemap.js';\nimport { buildUrlBaseXml } from './builders/url-builder.js';\nimport { buildImageXml } from './builders/image-builder.js';\nimport { buildVideoXml } from './builders/video-builder.js';\nimport { buildNewsXml } from './builders/news-builder.js';\nimport { validateCrossFields } from './validation/cross-validator.js';\n\n/**\n * Génère le flux XML complet du sitemap incluant les extensions Images, Vidéos, News et Hreflang.\n * v1.1.9 : Intégration de la validation sémantique croisée en amont.\n */\nexport function generateXml(entries: SitemapEntry[], options: SitemapOptions = {}): string {\n const now = new Date().toISOString();\n let finalEntries = [...entries];\n\n if (options.sortByPriority) {\n finalEntries.sort((a, b) => {\n const priorityA = a.priority !== undefined ? (a.priority as number) : 0.5;\n const priorityB = b.priority !== undefined ? (b.priority as number) : 0.5;\n return priorityB - priorityA;\n });\n }\n \n let xml = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n`;\n xml += `<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\"\\n`;\n xml += ` xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\"\\n`;\n xml += ` xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\"\\n`;\n xml += ` xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\"\\n`;\n xml += ` xmlns:xhtml=\"http://www.w3.org/1999/xhtml\">\\n`;\n\n for (const entry of finalEntries) {\n // 🔥 ÉTAPE CRUCIALE v1.1.9 : Validation croisée pré-génération\n validateCrossFields(entry);\n\n xml += ` <url>\\n`;\n \n // 1. Éléments de base et hreflang alternatifs\n xml += buildUrlBaseXml(entry, options, now);\n\n // 2. Extension Images Google\n xml += buildImageXml(entry.images);\n\n // 3. Extension Vidéos Google (Validations v1.1.3 & v1.1.4 intégrées)\n xml += buildVideoXml(entry.videos);\n\n // 4. Extension News Google\n xml += buildNewsXml(entry.news);\n\n xml += ` </url>\\n`;\n }\n\n xml += `</urlset>`;\n return xml;\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapIndexEntry } from '../../types/sitemap.js';\nimport { escapeXml } from '../../utils/xml-escape.js';\nimport { sanitizeAndValidateUrl } from './url-builder.js';\n\n/**\n * Génère la structure brute XML pour un fichier d'indexation de sitemaps.\n */\nexport function buildSitemapIndexXml(entries: SitemapIndexEntry[]): string {\n let xml = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n`;\n xml += `<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\\n`;\n\n for (const entry of entries) {\n const cleanLoc = sanitizeAndValidateUrl(entry.loc, 'sitemap index location');\n \n xml += ` <sitemap>\\n`;\n xml += ` <loc>${escapeXml(cleanLoc)}</loc>\\n`;\n \n if (entry.lastmod) {\n const date = entry.lastmod instanceof Date ? entry.lastmod.toISOString() : entry.lastmod;\n xml += ` <lastmod>${date}</lastmod>\\n`;\n }\n \n xml += ` </sitemap>\\n`;\n }\n\n xml += `</sitemapindex>`;\n return xml;\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry, SitemapOptions, SitemapIndexEntry } from './types/sitemap.js';\nimport { generateXml } from './core/generator.js';\nimport { buildSitemapIndexXml } from './core/builders/index-builder.js';\n\nexport * from './types/sitemap.js';\n\n/**\n * Génère une réponse HTTP compatible Next.js (App Router) avec options de configuration.\n * v1.0.9 : Injection dynamique et personnalisable de l'en-tête Cache-Control via l'option maxAge.\n * \n * @param entries - Liste des entrées du sitemap\n * @param options - Options de génération et de mise en cache (ex: autoLastmod, maxAge)\n * @returns Une instance de Response contenant le flux XML configuré\n */\nexport function getServerSitemapResponse(\n entries: SitemapEntry[], \n options: SitemapOptions = {}\n): Response {\n const xml = generateXml(entries, options);\n\n // Détermination de la stratégie de mise en cache (v1.0.9)\n const cacheControlHeader = options.maxAge !== undefined\n ? `public, max-age=${options.maxAge}, must-revalidate`\n : 'public, s-maxage=86400, stale-while-revalidate';\n\n return new Response(xml, {\n headers: {\n 'Content-Type': 'application/xml',\n 'Cache-Control': cacheControlHeader,\n },\n });\n}\n\n/**\n * ✨ v1.2.0 : Génère une réponse HTTP Next.js (App Router) pour un Index de Sitemaps.\n * Permet de lister et de regrouper plusieurs sous-fichiers XML sitemaps.\n * \n * @param entries - Liste des sous-sitemaps composant l'index\n * @param options - Options de configuration (ex: maxAge pour le cache)\n * @returns Une instance de Response contenant le flux XML de l'index\n */\nexport function getServerSitemapIndexResponse(\n entries: SitemapIndexEntry[],\n options: Pick<SitemapOptions, 'maxAge'> = {}\n): Response {\n const xml = buildSitemapIndexXml(entries);\n\n // Détermination de la stratégie de mise en cache (v1.2.0)\n const cacheControlHeader = options.maxAge !== undefined\n ? `public, max-age=${options.maxAge}, must-revalidate`\n : 'public, s-maxage=86400, stale-while-revalidate';\n\n return new Response(xml, {\n headers: {\n 'Content-Type': 'application/xml',\n 'Cache-Control': cacheControlHeader,\n },\n });\n}"],"mappings":"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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 '<';\n case '>': return '>';\n case '&': return '&';\n case '\"': return '"';\n case \"'\": return ''';\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.2.
|
|
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",
|