next-advanced-sitemap 1.1.9 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -36
- package/dist/index.cjs +46 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +23 -3
- package/dist/index.d.ts +23 -3
- package/dist/index.js +45 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,14 +3,16 @@
|
|
|
3
3
|
[](LICENSE)
|
|
4
4
|

|
|
5
5
|
|
|
6
|
-
A robust and type-safe sitemap generator for Next.js (App Router). This library extends standard sitemap capabilities by providing native support for Google-specific metadata including Images, Videos, News,
|
|
6
|
+
A robust and type-safe sitemap generator for Next.js (App Router). This library extends standard sitemap capabilities by providing native support for Google-specific metadata including Images, Videos, News, Internationalization (Hreflang), and large-scale Sitemap Index structures.
|
|
7
7
|
|
|
8
8
|
## Overview
|
|
9
9
|
|
|
10
|
-
While Next.js provides a built-in `MetadataRoute.Sitemap` utility, it currently lacks support for advanced SEO attributes required by high-performance web applications. `next-advanced-sitemap` bridges this gap, allowing developers to programmatically generate complex XML sitemaps that comply with Google's extended schemas.
|
|
10
|
+
While Next.js provides a built-in `MetadataRoute.Sitemap` utility, it currently lacks support for advanced SEO attributes required by high-performance web applications. `next-advanced-sitemap` bridges this gap, allowing developers to programmatically generate complex XML sitemaps and indexes that comply with Google's extended schemas.
|
|
11
11
|
|
|
12
12
|
## Features
|
|
13
13
|
|
|
14
|
+
- **Universal XML Namespace Injection & Strict Index Guardrails (v1.2.2)**: Automated compliance matching that embeds standard canonical namespaces (`xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"`) inside root index configurations. Prevents parsing errors or validation dropouts across alternative search crawlers like Bing, Yandex, or DuckDuckGo while routing individual child locations through strict syntax URL engines.
|
|
15
|
+
- **Native Sitemap Indexing Architecture (v1.2.0)**: Advanced support for sitemap index grouping (`getServerSitemapIndexResponse`). Allows seamless scaling by linking multiple sub-sitemaps (e.g., `sitemap-0.xml`, `sitemap-products.xml`) under a centralized endpoint to bypass Google's 50,000 URLs strict limitation.
|
|
14
16
|
- **Cross-Field Semantic Validation (v1.1.9)**: Native cross-field validation engine that intercepts logical data contradictions (e.g., Live streams with static durations, subscriptions conflicts, or expired news) before writing the XML stream. Guarantees a flawless 100% compliance score in Google Search Console.
|
|
15
17
|
- **Financial Google News Syndication (v1.1.8)**: Native support for `<news:stock_tickers>` tags, mapping general press articles directly to active global stock market boards.
|
|
16
18
|
- **Video Semantic Classification & Long-Tail SEO (v1.1.7)**: Support for `<video:category>` and multiple `<video:tag>` elements to deeply contextualize video content and map assets to highly targeted niche queries.
|
|
@@ -42,7 +44,9 @@ npm install next-advanced-sitemap
|
|
|
42
44
|
|
|
43
45
|
## Usage
|
|
44
46
|
|
|
45
|
-
|
|
47
|
+
### 1. Generating a Sub-Sitemap (Standard XML)
|
|
48
|
+
|
|
49
|
+
To implement a rich structural sitemap in the Next.js App Router, create a Route Handler at `app/sitemap-records.xml/route.ts`.
|
|
46
50
|
|
|
47
51
|
```typescript
|
|
48
52
|
import { getServerSitemapResponse, SitemapEntry } from 'next-advanced-sitemap';
|
|
@@ -70,22 +74,13 @@ export async function GET() {
|
|
|
70
74
|
publication_date: new Date(),
|
|
71
75
|
duration: 7200,
|
|
72
76
|
view_count: 25000,
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
tags: ['nextjs', 'typescript', ' advanced seo '], // Limited to 32 tags max
|
|
76
|
-
// v1.1.6: Commercial VOD Pricing Structure (Auto ISO 4217 & decimal formatting)
|
|
77
|
+
category: 'Education & Technology',
|
|
78
|
+
tags: ['nextjs', 'typescript', 'advanced seo'],
|
|
77
79
|
price: { value: 19.99, currency: 'usd', type: 'own' },
|
|
78
|
-
// v1.1.5: Flexible Paywall Registration (Accepts boolean or strict 'yes' | 'no')
|
|
79
80
|
requires_subscription: true,
|
|
80
|
-
// v1.1.4: Strict Geographic Filtering & Capitalization Sanitization
|
|
81
81
|
restriction: {
|
|
82
82
|
relationship: 'allow',
|
|
83
|
-
countries: ['cd', 'fr', 'us']
|
|
84
|
-
},
|
|
85
|
-
// v1.1.4: Native Screen-Class Targeting Controls
|
|
86
|
-
platform: {
|
|
87
|
-
relationship: 'deny',
|
|
88
|
-
platforms: ['tv'] // Deny indexing out for Smart TV layouts
|
|
83
|
+
countries: ['cd', 'fr', 'us']
|
|
89
84
|
}
|
|
90
85
|
}
|
|
91
86
|
]
|
|
@@ -93,31 +88,16 @@ export async function GET() {
|
|
|
93
88
|
{
|
|
94
89
|
url: 'https://fomadev.com/news/fintech-drc-2026',
|
|
95
90
|
priority: 0.85,
|
|
96
|
-
// v1.1.8: Google News Syndication Matrix with Market Stock Tickers
|
|
97
91
|
news: {
|
|
98
92
|
name: 'FomaDev Insights',
|
|
99
93
|
language: 'fr',
|
|
100
|
-
publication_date: new Date(),
|
|
94
|
+
publication_date: new Date(), // Always dynamic to meet the strict 48h rule (v1.1.9)
|
|
101
95
|
title: 'The Rise of FinTech Infrastructure in Central Africa',
|
|
102
|
-
stock_tickers: ['
|
|
96
|
+
stock_tickers: ['NYSE:BABA', 'NASDAQ:AAPL']
|
|
103
97
|
}
|
|
104
|
-
},
|
|
105
|
-
{
|
|
106
|
-
url: 'https://fomadev.com/products/tech-item',
|
|
107
|
-
priority: 0.8,
|
|
108
|
-
images: [
|
|
109
|
-
{
|
|
110
|
-
loc: 'https://fomadev.com/images/product.png',
|
|
111
|
-
title: ' Premium Wireless Keyboard ', // v1.1.2: Auto-trimmed preventively
|
|
112
|
-
caption: 'Close-up shot of our custom mechanical keyboard layout with XML characters like & or <', // v1.1.2: Deep XML Escaping
|
|
113
|
-
geo_location: 'Kinshasa, Democratic Republic of the Congo', // v1.1.0 Local SEO
|
|
114
|
-
license: 'https://fomadev.com/terms/licensing' // v1.1.0 Badging
|
|
115
|
-
}
|
|
116
|
-
]
|
|
117
98
|
}
|
|
118
99
|
];
|
|
119
100
|
|
|
120
|
-
// Enable autoLastmod and sortByPriority (v1.0.8) to optimize crawl efficiency
|
|
121
101
|
return getServerSitemapResponse(entries, {
|
|
122
102
|
autoLastmod: true,
|
|
123
103
|
sortByPriority: true
|
|
@@ -125,19 +105,49 @@ export async function GET() {
|
|
|
125
105
|
}
|
|
126
106
|
```
|
|
127
107
|
|
|
108
|
+
### 2. Generating a Master Sitemap Index (v1.2.0)
|
|
109
|
+
|
|
110
|
+
When scaling up your application, group multiple sub-sitemaps dynamically. Create a Route Handler at `app/sitemap.xml/route.ts`.
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
import { getServerSitemapIndexResponse, SitemapIndexEntry } from 'next-advanced-sitemap';
|
|
114
|
+
|
|
115
|
+
export async function GET() {
|
|
116
|
+
const subSitemaps: SitemapIndexEntry[] = [
|
|
117
|
+
{
|
|
118
|
+
loc: 'https://fomadev.com/sitemap-records.xml',
|
|
119
|
+
lastmod: new Date() // Supports native JavaScript Date polymorphism
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
loc: 'https://fomadev.com/sitemap-products.xml',
|
|
123
|
+
lastmod: '2026-07-04T12:00:00.000Z'
|
|
124
|
+
}
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
// Serves a structural <sitemapindex> with optimal CDN caching headers
|
|
128
|
+
return getServerSitemapIndexResponse(subSitemaps, {
|
|
129
|
+
maxAge: 3600 // Custom cache eviction lifespan (optional)
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
128
134
|
## API Reference
|
|
129
135
|
|
|
136
|
+
### getServerSitemapIndexResponse(entries: SitemapIndexEntry[], options?: Pick<SitemapOptions, 'maxAge'>)
|
|
137
|
+
|
|
138
|
+
**Introduced in v1.2.0**. Generates a Next.js `Response` instance wrapping a structural `<sitemapindex>` tree. Ideal for routing deep content clusters while maintaining custom Edge cache distributions.
|
|
139
|
+
|
|
130
140
|
### getServerSitemapResponse(entries: SitemapEntry[], options?: SitemapOptions)
|
|
131
141
|
|
|
132
142
|
Generates a standard Next.js `Response` object with the correct `application/xml` content-type and optimized cache headers.
|
|
133
143
|
|
|
134
|
-
### Options:
|
|
144
|
+
### Options Matrix:
|
|
135
145
|
|
|
136
|
-
* `autoLastmod` (boolean): If `true`, injects the current ISO date for any entry missing the `lastmod` property.
|
|
146
|
+
* `autoLastmod` (boolean): If `true`, injects the current ISO date for any standard entry missing the `lastmod` property.
|
|
137
147
|
|
|
138
|
-
* `sortByPriority` (boolean): If `true`, sorts
|
|
148
|
+
* `sortByPriority` (boolean): If `true`, sorts standard records in a descending sequence based on priority level (`1.0` down to `0.0`).
|
|
139
149
|
|
|
140
|
-
* `maxAge` (number): (Optional) Maximum lifespan duration expressed in seconds. Transforms the HTTP communication layer payload to use a rigid `public, max-age=X, must-revalidate` schema.
|
|
150
|
+
* `maxAge` (number): (Optional) Maximum lifespan duration expressed in seconds. Transforms the HTTP communication layer payload to use a rigid `public, max-age=X, must-revalidate` schema. Supported by both standard and index responses.
|
|
141
151
|
|
|
142
152
|
### SitemapEntry Object
|
|
143
153
|
|
|
@@ -328,6 +338,19 @@ Generates a standard Next.js `Response` object with the correct `application/xml
|
|
|
328
338
|
|
|
329
339
|
## Technical Implementation
|
|
330
340
|
|
|
341
|
+
### Explicit Index Namespace Ingestion & Cross-Engine Interoperability (v1.2.2)
|
|
342
|
+
To secure discovery velocity across alternative crawlers (e.g., Bingbot) that reject unmapped root metadata structures, **v1.2.2** enforces strict compliance standards onto index generation trees:
|
|
343
|
+
|
|
344
|
+
* **Authoritative Schema Delivery**: The generator automatically injects the structural `xmlns` URI schema into the core `<sitemapindex>` element. This prevents alternative parsers from treating the output as an unindexed plain document.
|
|
345
|
+
* **Unified Normalization Layer**: Consolidates fault-tolerant property fallback mapping (recovering from `.url` variations seamlessly) and applies rigid validation patterns to intercept faulty localizations prior to production rendering.
|
|
346
|
+
|
|
347
|
+
### Native Sitemap Indexing Architecture & Edge Cache Alignment (v1.2.0)
|
|
348
|
+
To comfortably scale applications past search engine structural thresholds (max 50,000 URLs or 50MB per single uncompressed file), **v1.2.0** introduces a high-performance orchestration layer dedicated to nested sitemap index tree structures (`<sitemapindex>`):
|
|
349
|
+
|
|
350
|
+
* **Isolated Composition Engine**: The index builder avoids loading heavy polymorphic page matrices into memory. Instead, it relies on an ultra-lightweight serialization pipeline (`buildSitemapIndexXml`) dedicated exclusively to mapping nested `.xml` target links.
|
|
351
|
+
* **Shared Platform-Level Security**: Rather than implementing loose string references, the location processor (`loc`) is strictly routed through the core platform URL verification matrix. This intercepts structural mistakes, protocol anomalies (e.g. `ftp://`), and unencoded spaces before emitting a broken index payload.
|
|
352
|
+
* **Unified CDN Distribution Controls**: Both standard sitemaps and index structures share identical cache configuration capabilities. By default, index handlers emit immutable Edge CDN optimization directives (`public, s-maxage=86400, stale-while-revalidate`), while seamlessly unlocking manual invalidation windows via selective type extraction (`Pick<SitemapOptions, 'maxAge'>`).
|
|
353
|
+
|
|
331
354
|
### Cross-Field Semantic Validation & Search Console Guarantees (v1.1.9)
|
|
332
355
|
To enforce an absolute 100% SEO health score and completely prevent index drops caused by structural logic contradictions, **v1.1.9** introduces an isolated pre-generation validation layer (`validateCrossFields`). The core engine scans entry matrices and enforces strict cross-field business rules:
|
|
333
356
|
|
package/dist/index.cjs
CHANGED
|
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
getServerSitemapIndexResponse: () => getServerSitemapIndexResponse,
|
|
23
24
|
getServerSitemapResponse: () => getServerSitemapResponse
|
|
24
25
|
});
|
|
25
26
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -408,13 +409,17 @@ function generateXml(entries, options = {}) {
|
|
|
408
409
|
xml += ` xmlns:xhtml="http://www.w3.org/1999/xhtml">
|
|
409
410
|
`;
|
|
410
411
|
for (const entry of finalEntries) {
|
|
411
|
-
|
|
412
|
+
const normalizedEntry = { ...entry };
|
|
413
|
+
if (entry.video && !normalizedEntry.videos) {
|
|
414
|
+
normalizedEntry.videos = [entry.video];
|
|
415
|
+
}
|
|
416
|
+
validateCrossFields(normalizedEntry);
|
|
412
417
|
xml += ` <url>
|
|
413
418
|
`;
|
|
414
|
-
xml += buildUrlBaseXml(
|
|
415
|
-
xml += buildImageXml(
|
|
416
|
-
xml += buildVideoXml(
|
|
417
|
-
xml += buildNewsXml(
|
|
419
|
+
xml += buildUrlBaseXml(normalizedEntry, options, now);
|
|
420
|
+
xml += buildImageXml(normalizedEntry.images);
|
|
421
|
+
xml += buildVideoXml(normalizedEntry.videos);
|
|
422
|
+
xml += buildNewsXml(normalizedEntry.news);
|
|
418
423
|
xml += ` </url>
|
|
419
424
|
`;
|
|
420
425
|
}
|
|
@@ -422,6 +427,31 @@ function generateXml(entries, options = {}) {
|
|
|
422
427
|
return xml;
|
|
423
428
|
}
|
|
424
429
|
|
|
430
|
+
// src/core/builders/index-builder.ts
|
|
431
|
+
function buildSitemapIndexXml(entries) {
|
|
432
|
+
let xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
433
|
+
`;
|
|
434
|
+
xml += `<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
435
|
+
`;
|
|
436
|
+
for (const entry of entries) {
|
|
437
|
+
const targetLoc = entry.loc || entry.url || "";
|
|
438
|
+
const cleanLoc = sanitizeAndValidateUrl(targetLoc, "sitemap index location");
|
|
439
|
+
xml += ` <sitemap>
|
|
440
|
+
`;
|
|
441
|
+
xml += ` <loc>${escapeXml(cleanLoc)}</loc>
|
|
442
|
+
`;
|
|
443
|
+
if (entry.lastmod) {
|
|
444
|
+
const date = entry.lastmod instanceof Date ? entry.lastmod.toISOString() : entry.lastmod;
|
|
445
|
+
xml += ` <lastmod>${date}</lastmod>
|
|
446
|
+
`;
|
|
447
|
+
}
|
|
448
|
+
xml += ` </sitemap>
|
|
449
|
+
`;
|
|
450
|
+
}
|
|
451
|
+
xml += `</sitemapindex>`;
|
|
452
|
+
return xml;
|
|
453
|
+
}
|
|
454
|
+
|
|
425
455
|
// src/index.ts
|
|
426
456
|
function getServerSitemapResponse(entries, options = {}) {
|
|
427
457
|
const xml = generateXml(entries, options);
|
|
@@ -433,8 +463,19 @@ function getServerSitemapResponse(entries, options = {}) {
|
|
|
433
463
|
}
|
|
434
464
|
});
|
|
435
465
|
}
|
|
466
|
+
function getServerSitemapIndexResponse(entries, options = {}) {
|
|
467
|
+
const xml = buildSitemapIndexXml(entries);
|
|
468
|
+
const cacheControlHeader = options.maxAge !== void 0 ? `public, max-age=${options.maxAge}, must-revalidate` : "public, s-maxage=86400, stale-while-revalidate";
|
|
469
|
+
return new Response(xml, {
|
|
470
|
+
headers: {
|
|
471
|
+
"Content-Type": "application/xml",
|
|
472
|
+
"Cache-Control": cacheControlHeader
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
}
|
|
436
476
|
// Annotate the CommonJS export names for ESM import in node:
|
|
437
477
|
0 && (module.exports = {
|
|
478
|
+
getServerSitemapIndexResponse,
|
|
438
479
|
getServerSitemapResponse
|
|
439
480
|
});
|
|
440
481
|
//# sourceMappingURL=index.cjs.map
|
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"],"sourcesContent":["/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry, SitemapOptions } from './types/sitemap.js';\nimport { generateXml } from './core/generator.js';\n\nexport * from './types/sitemap.js';\n\n/**\n * Génère une réponse HTTP compatible Next.js (App Router) avec options de configuration.\n * v1.0.9 : Injection dynamique et personnalisable de l'en-tête Cache-Control via l'option maxAge\n * * @param entries - Liste des entrées du sitemap\n * @param options - Options de génération et de mise en cache (ex: autoLastmod, maxAge)\n * @returns Une instance de Response contenant le flux XML configuré\n */\nexport function getServerSitemapResponse(\n entries: SitemapEntry[], \n options: SitemapOptions = {}\n): Response {\n const xml = generateXml(entries, options);\n\n // Détermination de la stratégie de mise en cache (v1.0.9)\n const cacheControlHeader = options.maxAge !== undefined\n ? `public, max-age=${options.maxAge}, must-revalidate`\n : 'public, s-maxage=86400, stale-while-revalidate';\n\n return new Response(xml, {\n headers: {\n 'Content-Type': 'application/xml',\n 'Cache-Control': cacheControlHeader,\n },\n });\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\n/**\n * Convertit les caractères spéciaux en entités XML pour éviter la corruption du fichier.\n * Gère : <, >, &, \", '\n */\nexport function escapeXml(unsafe: string | undefined | null): string {\n if (!unsafe) return '';\n \n return unsafe.replace(/[<>&\"']/g, (c) => {\n switch (c) {\n case '<': return '<';\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}"],"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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACSO,SAAS,UAAU,QAA2C;AACnE,MAAI,CAAC,OAAQ,QAAO;AAEpB,SAAO,OAAO,QAAQ,YAAY,CAAC,MAAM;AACvC,YAAQ,GAAG;AAAA,MACT,KAAK;AAAK,eAAO;AAAA,MACjB,KAAK;AAAK,eAAO;AAAA,MACjB,KAAK;AAAK,eAAO;AAAA,MACjB,KAAK;AAAK,eAAO;AAAA,MACjB,KAAK;AAAK,eAAO;AAAA,MACjB;AAAS,eAAO;AAAA,IAClB;AAAA,EACF,CAAC;AACH;;;ACVO,SAAS,uBAAuB,QAAgB,SAAyB;AAC9E,QAAM,MAAM,SAAS,OAAO,KAAK,IAAI;AAErC,MAAI,CAAC,IAAI,WAAW,SAAS,KAAK,CAAC,IAAI,WAAW,UAAU,GAAG;AAC7D,UAAM,IAAI;AAAA,MACR,0CAA0C,OAAO,MAAM,GAAG;AAAA,IAC5D;AAAA,EACF;AAEA,MAAI,IAAI,SAAS,GAAG,GAAG;AACrB,UAAM,IAAI;AAAA,MACR,+DAA+D,OAAO,MAAM,GAAG;AAAA,IACjF;AAAA,EACF;AAEA,MAAI,UAAU;AACd,MAAI,OAAO,IAAI,aAAa,YAAY;AACtC,cAAU,IAAI,SAAS,GAAG;AAAA,EAC5B,OAAO;AACL,QAAI;AACF,UAAI,IAAI,GAAG;AACX,gBAAU;AAAA,IACZ,QAAQ;AACN,gBAAU;AAAA,IACZ;AAAA,EACF;AAEA,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR,+DAA+D,OAAO,MAAM,GAAG;AAAA,IACjF;AAAA,EACF;AAEA,SAAO;AACT;AAKO,SAAS,gBAAgB,OAAqB,SAAyB,QAAwB;AACpG,MAAI,MAAM;AAEV,QAAM,eAAe,uBAAuB,MAAM,KAAK,YAAY;AACnE,SAAO,YAAY,UAAU,YAAY,CAAC;AAAA;AAE1C,MAAI,MAAM,YAAY,QAAQ;AAC5B,eAAW,OAAO,MAAM,YAAY;AAClC,YAAM,cAAc,uBAAuB,IAAI,MAAM,gBAAgB;AACrE,aAAO,6CAA6C,UAAU,IAAI,QAAQ,CAAC,WAAW,UAAU,WAAW,CAAC;AAAA;AAAA,IAC9G;AAAA,EACF;AAEA,MAAI,eAAe,MAAM;AACzB,MAAI,QAAQ,eAAe,CAAC,cAAc;AACxC,mBAAe;AAAA,EACjB;AAEA,MAAI,cAAc;AAChB,UAAM,OAAO,wBAAwB,OAAO,aAAa,YAAY,IAAI;AACzE,WAAO,gBAAgB,IAAI;AAAA;AAAA,EAC7B;AAEA,MAAI,MAAM,YAAY;AACpB,WAAO,mBAAmB,MAAM,UAAU;AAAA;AAAA,EAC5C;AAEA,MAAI,MAAM,aAAa,QAAW;AAChC,WAAO,iBAAkB,MAAM,SAAoB,QAAQ,CAAC,CAAC;AAAA;AAAA,EAC/D;AAEA,SAAO;AACT;;;AC1EO,SAAS,cAAc,QAAwC;AACpE,MAAI,CAAC,QAAQ,OAAQ,QAAO;AAE5B,MAAI,MAAM;AACV,aAAW,OAAO,QAAQ;AACxB,UAAM,cAAc,uBAAuB,IAAI,KAAK,gBAAgB;AAEpE,WAAO;AAAA;AACP,WAAO,oBAAoB,UAAU,WAAW,CAAC;AAAA;AAEjD,QAAI,IAAI,SAAS,IAAI,MAAM,KAAK,MAAM,IAAI;AACxC,aAAO,sBAAsB,UAAU,IAAI,MAAM,KAAK,CAAC,CAAC;AAAA;AAAA,IAC1D;AACA,QAAI,IAAI,WAAW,IAAI,QAAQ,KAAK,MAAM,IAAI;AAC5C,aAAO,wBAAwB,UAAU,IAAI,QAAQ,KAAK,CAAC,CAAC;AAAA;AAAA,IAC9D;AAEA,QAAI,IAAI,gBAAgB,IAAI,aAAa,KAAK,MAAM,IAAI;AACtD,aAAO,6BAA6B,UAAU,IAAI,aAAa,KAAK,CAAC,CAAC;AAAA;AAAA,IACxE;AAEA,QAAI,IAAI,SAAS;AACf,YAAM,kBAAkB,uBAAuB,IAAI,SAAS,mBAAmB;AAC/E,aAAO,wBAAwB,UAAU,eAAe,CAAC;AAAA;AAAA,IAC3D;AAEA,WAAO;AAAA;AAAA,EACT;AAEA,SAAO;AACT;;;AC9BO,SAAS,cAAc,QAAwC;AACpE,MAAI,CAAC,QAAQ,OAAQ,QAAO;AAE5B,MAAI,MAAM;AACV,aAAW,OAAO,QAAQ;AACxB,UAAM,gBAAgB,uBAAuB,IAAI,eAAe,iBAAiB;AACjF,UAAM,kBAAkB,IAAI,cAAc,uBAAuB,IAAI,aAAa,wBAAwB,IAAI;AAC9G,UAAM,iBAAiB,IAAI,aAAa,uBAAuB,IAAI,YAAY,uBAAuB,IAAI;AAE1G,WAAO;AAAA;AACP,WAAO,8BAA8B,UAAU,aAAa,CAAC;AAAA;AAC7D,WAAO,sBAAsB,UAAU,IAAI,KAAK,CAAC;AAAA;AACjD,WAAO,4BAA4B,UAAU,IAAI,WAAW,CAAC;AAAA;AAE7D,QAAI,gBAAiB,QAAO,4BAA4B,UAAU,eAAe,CAAC;AAAA;AAClF,QAAI,eAAgB,QAAO,2BAA2B,UAAU,cAAc,CAAC;AAAA;AAE/E,QAAI,IAAI,kBAAkB;AACxB,YAAM,QAAQ,IAAI,4BAA4B,OAAO,IAAI,iBAAiB,YAAY,IAAI,IAAI;AAC9F,aAAO,iCAAiC,KAAK;AAAA;AAAA,IAC/C;AAGA,QAAI,IAAI,aAAa,QAAW;AAC9B,YAAM,gBAAgB,KAAK,MAAM,IAAI,QAAQ;AAC7C,UAAI,gBAAgB,KAAK,gBAAgB,OAAO;AAC9C,cAAM,IAAI;AAAA,UACR,mDAAmD,aAAa;AAAA,QAClE;AAAA,MACF;AACA,aAAO,yBAAyB,aAAa;AAAA;AAAA,IAC/C;AAGA,QAAI,IAAI,eAAe,QAAW;AAChC,YAAM,iBAAiB,KAAK,MAAM,IAAI,UAAU;AAChD,UAAI,iBAAiB,GAAG;AACtB,cAAM,IAAI;AAAA,UACR,qDAAqD,cAAc;AAAA,QACrE;AAAA,MACF;AACA,aAAO,2BAA2B,cAAc;AAAA;AAAA,IAClD;AAEA,QAAI,IAAI,MAAM;AACZ,aAAO,qBAAqB,IAAI,IAAI;AAAA;AAAA,IACtC;AAGA,QAAI,IAAI,aAAa;AACnB,UAAI,CAAC,IAAI,YAAY,aAAa,IAAI,YAAY,UAAU,WAAW,GAAG;AACxE,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAEA,YAAM,iBAAiB,IAAI,YAAY,UAAU,IAAI,aAAW;AAC9D,cAAM,OAAO,QAAQ,KAAK,EAAE,YAAY;AACxC,YAAI,KAAK,SAAS,KAAK,KAAK,SAAS,GAAG;AACtC,gBAAM,IAAI;AAAA,YACR,+DAA+D,OAAO;AAAA,UACxE;AAAA,QACF;AACA,eAAO;AAAA,MACT,CAAC;AAED,YAAM,eAAe,eAAe,KAAK,GAAG;AAC5C,aAAO,0CAA0C,IAAI,YAAY,YAAY,KAAK,YAAY;AAAA;AAAA,IAChG;AAGA,QAAI,IAAI,UAAU;AAChB,UAAI,CAAC,IAAI,SAAS,aAAa,IAAI,SAAS,UAAU,WAAW,GAAG;AAClE,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAEA,YAAM,iBAAiB,CAAC,OAAO,UAAU,IAAI;AAC7C,iBAAW,KAAK,IAAI,SAAS,WAAW;AACtC,YAAI,CAAC,eAAe,SAAS,CAAC,GAAG;AAC/B,gBAAM,IAAI;AAAA,YACR,mDAAmD,CAAC;AAAA,UACtD;AAAA,QACF;AAAA,MACF;AAEA,YAAM,eAAe,IAAI,SAAS,UAAU,KAAK,GAAG;AACpD,aAAO,uCAAuC,IAAI,SAAS,YAAY,KAAK,YAAY;AAAA;AAAA,IAC1F;AAEA,QAAI,IAAI,0BAA0B,QAAW;AAC3C,UAAI;AAEJ,UAAI,OAAO,IAAI,0BAA0B,WAAW;AAClD,mBAAW,IAAI,wBAAwB,QAAQ;AAAA,MACjD,WAAW,IAAI,0BAA0B,SAAS,IAAI,0BAA0B,MAAM;AACpF,mBAAW,IAAI;AAAA,MACjB,OAAO;AACL,cAAM,IAAI;AAAA,UACR,qEAAqE,IAAI,qBAAqB;AAAA,QAChG;AAAA,MACF;AAEA,aAAO,sCAAsC,QAAQ;AAAA;AAAA,IACvD;AAGA,QAAI,IAAI,OAAO;AACb,YAAM,EAAE,OAAO,UAAU,KAAK,IAAI,IAAI;AAEtC,UAAI,UAAU,UAAa,QAAQ,GAAG;AACpC,cAAM,IAAI;AAAA,UACR,uDAAuD,KAAK;AAAA,QAC9D;AAAA,MACF;AAEA,YAAM,gBAAgB,WAAW,SAAS,KAAK,EAAE,YAAY,IAAI;AACjE,UAAI,cAAc,WAAW,GAAG;AAC9B,cAAM,IAAI;AAAA,UACR,4DAA4D,QAAQ;AAAA,QACtE;AAAA,MACF;AAEA,UAAI,WAAW,gCAAgC,aAAa;AAC5D,UAAI,MAAM;AACR,YAAI,SAAS,UAAU,SAAS,OAAO;AACrC,gBAAM,IAAI;AAAA,YACR,gDAAgD,IAAI;AAAA,UACtD;AAAA,QACF;AACA,oBAAY,UAAU,IAAI;AAAA,MAC5B;AACA,kBAAY,IAAI,MAAM,QAAQ,CAAC,CAAC;AAAA;AAEhC,aAAO;AAAA,IACT;AAGA,QAAI,IAAI,aAAa,QAAW;AAC9B,YAAM,gBAAgB,IAAI,SAAS,KAAK;AACxC,UAAI,CAAC,eAAe;AAClB,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AACA,UAAI,cAAc,SAAS,KAAK;AAC9B,cAAM,IAAI;AAAA,UACR,0DAA0D,cAAc,MAAM;AAAA,QAChF;AAAA,MACF;AACA,aAAO,yBAAyB,UAAU,aAAa,CAAC;AAAA;AAAA,IAC1D;AAGA,QAAI,IAAI,MAAM;AACZ,UAAI,IAAI,KAAK,SAAS,IAAI;AACxB,cAAM,IAAI;AAAA,UACR,qDAAqD,IAAI,KAAK,MAAM;AAAA,QACtE;AAAA,MACF;AAEA,iBAAW,OAAO,IAAI,MAAM;AAC1B,cAAM,WAAW,IAAI,KAAK;AAC1B,YAAI,CAAC,UAAU;AACb,gBAAM,IAAI;AAAA,YACR;AAAA,UACF;AAAA,QACF;AACA,eAAO,oBAAoB,UAAU,QAAQ,CAAC;AAAA;AAAA,MAChD;AAAA,IACF;AAEA,WAAO;AAAA;AAAA,EACT;AAEA,SAAO;AACT;;;AClLO,SAAS,aAAa,MAAoC;AAC/D,MAAI,CAAC,KAAM,QAAO;AAElB,QAAM,QAAQ,KAAK,4BAA4B,OAAO,KAAK,iBAAiB,YAAY,IAAI,KAAK;AAEjG,MAAI,MAAM;AACV,SAAO;AAAA;AACP,SAAO;AAAA;AACP,SAAO,sBAAsB,UAAU,KAAK,IAAI,CAAC;AAAA;AACjD,SAAO,0BAA0B,UAAU,KAAK,QAAQ,CAAC;AAAA;AACzD,SAAO;AAAA;AACP,SAAO,gCAAgC,KAAK;AAAA;AAC5C,SAAO,qBAAqB,UAAU,KAAK,KAAK,CAAC;AAAA;AAGjD,MAAI,KAAK,eAAe;AACtB,QAAI,CAAC,MAAM,QAAQ,KAAK,aAAa,GAAG;AACtC,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,UAAM,mBAAmB,KAAK,cAC3B,IAAI,YAAU;AACb,YAAM,cAAc,OAAO,KAAK;AAChC,UAAI,CAAC,aAAa;AAChB,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AACA,UAAI,CAAC,YAAY,SAAS,GAAG,GAAG;AAC9B,cAAM,IAAI;AAAA,UACR,yDAAyD,WAAW;AAAA,QACtE;AAAA,MACF;AACA,aAAO;AAAA,IACT,CAAC;AAEH,QAAI,iBAAiB,SAAS,GAAG;AAE/B,YAAM,gBAAgB,iBAAiB,KAAK,GAAG;AAC/C,aAAO,6BAA6B,UAAU,aAAa,CAAC;AAAA;AAAA,IAC9D;AAAA,EACF;AAEA,SAAO;AAAA;AAEP,SAAO;AACT;;;AC7CO,SAAS,oBAAoB,OAA2B;AAE7D,MAAI,MAAM,UAAU,MAAM,OAAO,SAAS,GAAG;AAC3C,eAAW,OAAO,MAAM,QAAQ;AAG9B,UAAI,IAAI,SAAS,SAAS,IAAI,aAAa,UAAa,IAAI,WAAW,GAAG;AACxE,cAAM,IAAI;AAAA,UACR,gEAAgE,MAAM,GAAG;AAAA,QAC3E;AAAA,MACF;AAGA,UAAI,IAAI,0BAA0B,SAAS,IAAI,SAAS,IAAI,MAAM,SAAS,OAAO;AAChF,cAAM,IAAI;AAAA,UACR,gEAAgE,MAAM,GAAG;AAAA,QAC3E;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,MAAI,MAAM,MAAM;AACd,UAAM,UAAU,MAAM,KAAK,4BAA4B,OACnD,MAAM,KAAK,mBACX,IAAI,KAAK,MAAM,KAAK,gBAAgB;AAExC,UAAM,MAAM,oBAAI,KAAK;AACrB,UAAM,WAAW,IAAI,QAAQ,IAAI,QAAQ,QAAQ;AACjD,UAAM,aAAa,YAAY,MAAO,KAAK,KAAK;AAGhD,QAAI,aAAa,GAAG;AAClB,YAAM,IAAI;AAAA,QACR,gEAAgE,MAAM,GAAG,qGAAqG,WAAW,QAAQ,CAAC,CAAC;AAAA,MACrM;AAAA,IACF;AAAA,EACF;AACF;;;ACjCO,SAAS,YAAY,SAAyB,UAA0B,CAAC,GAAW;AACzF,QAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,MAAI,eAAe,CAAC,GAAG,OAAO;AAE9B,MAAI,QAAQ,gBAAgB;AAC1B,iBAAa,KAAK,CAAC,GAAG,MAAM;AAC1B,YAAM,YAAY,EAAE,aAAa,SAAa,EAAE,WAAsB;AACtE,YAAM,YAAY,EAAE,aAAa,SAAa,EAAE,WAAsB;AACtE,aAAO,YAAY;AAAA,IACrB,CAAC;AAAA,EACH;AAEA,MAAI,MAAM;AAAA;AACV,SAAO;AAAA;AACP,SAAO;AAAA;AACP,SAAO;AAAA;AACP,SAAO;AAAA;AACP,SAAO;AAAA;AAEP,aAAW,SAAS,cAAc;AAGhC,UAAM,kBAAkB,EAAE,GAAG,MAAM;AAEnC,QAAK,MAAc,SAAS,CAAC,gBAAgB,QAAQ;AACnD,sBAAgB,SAAS,CAAG,MAAc,KAAM;AAAA,IAClD;AAGA,wBAAoB,eAAe;AAEnC,WAAO;AAAA;AAGP,WAAO,gBAAgB,iBAAiB,SAAS,GAAG;AAGpD,WAAO,cAAc,gBAAgB,MAAM;AAG3C,WAAO,cAAc,gBAAgB,MAAM;AAG3C,WAAO,aAAa,gBAAgB,IAAI;AAExC,WAAO;AAAA;AAAA,EACT;AAEA,SAAO;AACP,SAAO;AACT;;;ACrDO,SAAS,qBAAqB,SAAsC;AACzE,MAAI,MAAM;AAAA;AAEV,SAAO;AAAA;AAEP,aAAW,SAAS,SAAS;AAE3B,UAAM,YAAY,MAAM,OAAQ,MAAc,OAAO;AAGrD,UAAM,WAAW,uBAAuB,WAAW,wBAAwB;AAE3E,WAAO;AAAA;AACP,WAAO,YAAY,UAAU,QAAQ,CAAC;AAAA;AAEtC,QAAI,MAAM,SAAS;AACjB,YAAM,OAAO,MAAM,mBAAmB,OAAO,MAAM,QAAQ,YAAY,IAAI,MAAM;AACjF,aAAO,gBAAgB,IAAI;AAAA;AAAA,IAC7B;AAEA,WAAO;AAAA;AAAA,EACT;AAEA,SAAO;AACP,SAAO;AACT;;;ARnBO,SAAS,yBACd,SACA,UAA0B,CAAC,GACjB;AACV,QAAM,MAAM,YAAY,SAAS,OAAO;AAGxC,QAAM,qBAAqB,QAAQ,WAAW,SAC1C,mBAAmB,QAAQ,MAAM,sBACjC;AAEJ,SAAO,IAAI,SAAS,KAAK;AAAA,IACvB,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,iBAAiB;AAAA,IACnB;AAAA,EACF,CAAC;AACH;AAUO,SAAS,8BACd,SACA,UAA0C,CAAC,GACjC;AACV,QAAM,MAAM,qBAAqB,OAAO;AAGxC,QAAM,qBAAqB,QAAQ,WAAW,SAC1C,mBAAmB,QAAQ,MAAM,sBACjC;AAEJ,SAAO,IAAI,SAAS,KAAK;AAAA,IACvB,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,iBAAiB;AAAA,IACnB;AAAA,EACF,CAAC;AACH;","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -148,14 +148,34 @@ interface SitemapOptions {
|
|
|
148
148
|
*/
|
|
149
149
|
maxAge?: number;
|
|
150
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Interface pour une entrée individuelle dans un index de sitemaps
|
|
153
|
+
* @see https://developers.google.com/search/docs/crawling-indexing/sitemaps/large-sitemaps
|
|
154
|
+
*/
|
|
155
|
+
interface SitemapIndexEntry {
|
|
156
|
+
/** URL absolue du sous-sitemap (ex: 'https://fomadev.com/sitemap-vidéos.xml') */
|
|
157
|
+
loc: string;
|
|
158
|
+
/** Date de la dernière modification du sous-sitemap */
|
|
159
|
+
lastmod?: string | Date;
|
|
160
|
+
}
|
|
151
161
|
|
|
152
162
|
/**
|
|
153
163
|
* Génère une réponse HTTP compatible Next.js (App Router) avec options de configuration.
|
|
154
|
-
* v1.0.9 : Injection dynamique et personnalisable de l'en-tête Cache-Control via l'option maxAge
|
|
155
|
-
*
|
|
164
|
+
* v1.0.9 : Injection dynamique et personnalisable de l'en-tête Cache-Control via l'option maxAge.
|
|
165
|
+
*
|
|
166
|
+
* @param entries - Liste des entrées du sitemap
|
|
156
167
|
* @param options - Options de génération et de mise en cache (ex: autoLastmod, maxAge)
|
|
157
168
|
* @returns Une instance de Response contenant le flux XML configuré
|
|
158
169
|
*/
|
|
159
170
|
declare function getServerSitemapResponse(entries: SitemapEntry[], options?: SitemapOptions): Response;
|
|
171
|
+
/**
|
|
172
|
+
* ✨ v1.2.0 : Génère une réponse HTTP Next.js (App Router) pour un Index de Sitemaps.
|
|
173
|
+
* Permet de lister et de regrouper plusieurs sous-fichiers XML sitemaps.
|
|
174
|
+
*
|
|
175
|
+
* @param entries - Liste des sous-sitemaps composant l'index
|
|
176
|
+
* @param options - Options de configuration (ex: maxAge pour le cache)
|
|
177
|
+
* @returns Une instance de Response contenant le flux XML de l'index
|
|
178
|
+
*/
|
|
179
|
+
declare function getServerSitemapIndexResponse(entries: SitemapIndexEntry[], options?: Pick<SitemapOptions, 'maxAge'>): Response;
|
|
160
180
|
|
|
161
|
-
export { type SitemapAlternate, type SitemapChangeFreq, type SitemapEntry, type SitemapImage, type SitemapNews, type SitemapOptions, type SitemapPriority, type SitemapVideo, type VideoPlatform, type VideoPrice, type VideoRestriction, getServerSitemapResponse };
|
|
181
|
+
export { type SitemapAlternate, type SitemapChangeFreq, type SitemapEntry, type SitemapImage, type SitemapIndexEntry, type SitemapNews, type SitemapOptions, type SitemapPriority, type SitemapVideo, type VideoPlatform, type VideoPrice, type VideoRestriction, getServerSitemapIndexResponse, getServerSitemapResponse };
|
package/dist/index.d.ts
CHANGED
|
@@ -148,14 +148,34 @@ interface SitemapOptions {
|
|
|
148
148
|
*/
|
|
149
149
|
maxAge?: number;
|
|
150
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Interface pour une entrée individuelle dans un index de sitemaps
|
|
153
|
+
* @see https://developers.google.com/search/docs/crawling-indexing/sitemaps/large-sitemaps
|
|
154
|
+
*/
|
|
155
|
+
interface SitemapIndexEntry {
|
|
156
|
+
/** URL absolue du sous-sitemap (ex: 'https://fomadev.com/sitemap-vidéos.xml') */
|
|
157
|
+
loc: string;
|
|
158
|
+
/** Date de la dernière modification du sous-sitemap */
|
|
159
|
+
lastmod?: string | Date;
|
|
160
|
+
}
|
|
151
161
|
|
|
152
162
|
/**
|
|
153
163
|
* Génère une réponse HTTP compatible Next.js (App Router) avec options de configuration.
|
|
154
|
-
* v1.0.9 : Injection dynamique et personnalisable de l'en-tête Cache-Control via l'option maxAge
|
|
155
|
-
*
|
|
164
|
+
* v1.0.9 : Injection dynamique et personnalisable de l'en-tête Cache-Control via l'option maxAge.
|
|
165
|
+
*
|
|
166
|
+
* @param entries - Liste des entrées du sitemap
|
|
156
167
|
* @param options - Options de génération et de mise en cache (ex: autoLastmod, maxAge)
|
|
157
168
|
* @returns Une instance de Response contenant le flux XML configuré
|
|
158
169
|
*/
|
|
159
170
|
declare function getServerSitemapResponse(entries: SitemapEntry[], options?: SitemapOptions): Response;
|
|
171
|
+
/**
|
|
172
|
+
* ✨ v1.2.0 : Génère une réponse HTTP Next.js (App Router) pour un Index de Sitemaps.
|
|
173
|
+
* Permet de lister et de regrouper plusieurs sous-fichiers XML sitemaps.
|
|
174
|
+
*
|
|
175
|
+
* @param entries - Liste des sous-sitemaps composant l'index
|
|
176
|
+
* @param options - Options de configuration (ex: maxAge pour le cache)
|
|
177
|
+
* @returns Une instance de Response contenant le flux XML de l'index
|
|
178
|
+
*/
|
|
179
|
+
declare function getServerSitemapIndexResponse(entries: SitemapIndexEntry[], options?: Pick<SitemapOptions, 'maxAge'>): Response;
|
|
160
180
|
|
|
161
|
-
export { type SitemapAlternate, type SitemapChangeFreq, type SitemapEntry, type SitemapImage, type SitemapNews, type SitemapOptions, type SitemapPriority, type SitemapVideo, type VideoPlatform, type VideoPrice, type VideoRestriction, getServerSitemapResponse };
|
|
181
|
+
export { type SitemapAlternate, type SitemapChangeFreq, type SitemapEntry, type SitemapImage, type SitemapIndexEntry, type SitemapNews, type SitemapOptions, type SitemapPriority, type SitemapVideo, type VideoPlatform, type VideoPrice, type VideoRestriction, getServerSitemapIndexResponse, getServerSitemapResponse };
|
package/dist/index.js
CHANGED
|
@@ -382,13 +382,17 @@ function generateXml(entries, options = {}) {
|
|
|
382
382
|
xml += ` xmlns:xhtml="http://www.w3.org/1999/xhtml">
|
|
383
383
|
`;
|
|
384
384
|
for (const entry of finalEntries) {
|
|
385
|
-
|
|
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
|
}
|
|
@@ -396,6 +400,31 @@ function generateXml(entries, options = {}) {
|
|
|
396
400
|
return xml;
|
|
397
401
|
}
|
|
398
402
|
|
|
403
|
+
// src/core/builders/index-builder.ts
|
|
404
|
+
function buildSitemapIndexXml(entries) {
|
|
405
|
+
let xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
406
|
+
`;
|
|
407
|
+
xml += `<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
408
|
+
`;
|
|
409
|
+
for (const entry of entries) {
|
|
410
|
+
const targetLoc = entry.loc || entry.url || "";
|
|
411
|
+
const cleanLoc = sanitizeAndValidateUrl(targetLoc, "sitemap index location");
|
|
412
|
+
xml += ` <sitemap>
|
|
413
|
+
`;
|
|
414
|
+
xml += ` <loc>${escapeXml(cleanLoc)}</loc>
|
|
415
|
+
`;
|
|
416
|
+
if (entry.lastmod) {
|
|
417
|
+
const date = entry.lastmod instanceof Date ? entry.lastmod.toISOString() : entry.lastmod;
|
|
418
|
+
xml += ` <lastmod>${date}</lastmod>
|
|
419
|
+
`;
|
|
420
|
+
}
|
|
421
|
+
xml += ` </sitemap>
|
|
422
|
+
`;
|
|
423
|
+
}
|
|
424
|
+
xml += `</sitemapindex>`;
|
|
425
|
+
return xml;
|
|
426
|
+
}
|
|
427
|
+
|
|
399
428
|
// src/index.ts
|
|
400
429
|
function getServerSitemapResponse(entries, options = {}) {
|
|
401
430
|
const xml = generateXml(entries, options);
|
|
@@ -407,7 +436,18 @@ function getServerSitemapResponse(entries, options = {}) {
|
|
|
407
436
|
}
|
|
408
437
|
});
|
|
409
438
|
}
|
|
439
|
+
function getServerSitemapIndexResponse(entries, options = {}) {
|
|
440
|
+
const xml = buildSitemapIndexXml(entries);
|
|
441
|
+
const cacheControlHeader = options.maxAge !== void 0 ? `public, max-age=${options.maxAge}, must-revalidate` : "public, s-maxage=86400, stale-while-revalidate";
|
|
442
|
+
return new Response(xml, {
|
|
443
|
+
headers: {
|
|
444
|
+
"Content-Type": "application/xml",
|
|
445
|
+
"Cache-Control": cacheControlHeader
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
}
|
|
410
449
|
export {
|
|
450
|
+
getServerSitemapIndexResponse,
|
|
411
451
|
getServerSitemapResponse
|
|
412
452
|
};
|
|
413
453
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/xml-escape.ts","../src/core/builders/url-builder.ts","../src/core/builders/image-builder.ts","../src/core/builders/video-builder.ts","../src/core/builders/news-builder.ts","../src/core/validation/cross-validator.ts","../src/core/generator.ts","../src/index.ts"],"sourcesContent":["/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\n/**\n * Convertit les caractères spéciaux en entités XML pour éviter la corruption du fichier.\n * Gère : <, >, &, \", '\n */\nexport function escapeXml(unsafe: string | undefined | null): string {\n if (!unsafe) return '';\n \n return unsafe.replace(/[<>&\"']/g, (c) => {\n switch (c) {\n case '<': return '<';\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 { SitemapEntry, SitemapOptions } from './types/sitemap.js';\nimport { generateXml } from './core/generator.js';\n\nexport * from './types/sitemap.js';\n\n/**\n * Génère une réponse HTTP compatible Next.js (App Router) avec options de configuration.\n * v1.0.9 : Injection dynamique et personnalisable de l'en-tête Cache-Control via l'option maxAge\n * * @param entries - Liste des entrées du sitemap\n * @param options - Options de génération et de mise en cache (ex: autoLastmod, maxAge)\n * @returns Une instance de Response contenant le flux XML configuré\n */\nexport function getServerSitemapResponse(\n entries: SitemapEntry[], \n options: SitemapOptions = {}\n): Response {\n const xml = generateXml(entries, options);\n\n // Détermination de la stratégie de mise en cache (v1.0.9)\n const cacheControlHeader = options.maxAge !== undefined\n ? `public, max-age=${options.maxAge}, must-revalidate`\n : 'public, s-maxage=86400, stale-while-revalidate';\n\n return new Response(xml, {\n headers: {\n 'Content-Type': 'application/xml',\n 'Cache-Control': cacheControlHeader,\n },\n });\n}"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/utils/xml-escape.ts","../src/core/builders/url-builder.ts","../src/core/builders/image-builder.ts","../src/core/builders/video-builder.ts","../src/core/builders/news-builder.ts","../src/core/validation/cross-validator.ts","../src/core/generator.ts","../src/core/builders/index-builder.ts","../src/index.ts"],"sourcesContent":["/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\n/**\n * Convertit les caractères spéciaux en entités XML pour éviter la corruption du fichier.\n * Gère : <, >, &, \", '\n */\nexport function escapeXml(unsafe: string | undefined | null): string {\n if (!unsafe) return '';\n \n return unsafe.replace(/[<>&\"']/g, (c) => {\n switch (c) {\n case '<': return '<';\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.
|
|
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",
|