next-advanced-sitemap 1.1.4 → 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,8 +14,9 @@ While Next.js provides a built-in `MetadataRoute.Sitemap` utility, it currently
14
14
  - **Google Images Support**: Complete indexation of visual assets with support for titles, captions, local SEO positioning, and copyright protections.
15
15
  - **Image Accessibility Protection (v1.1.2)**: Advanced preventive protection against empty text strings or spaces (`.trim()`) in `title` and `caption` fields to completely eliminate malformed empty XML tokens.
16
16
  - **Google Video Support**: Boost video search layouts and video-carousel presence on Google Search with complete structured data encapsulation.
17
- - **Video Engagement Metrics & Validation (v1.1.3)**: Native integration of `<video:duration>` and `<video:view_count>` statistical metrics featuring deterministic float truncation (`Math.floor`) and strict bounding boundaries (0 to 28,800 seconds max).
17
+ - **Video Subscription & Paywall Guardrails (v1.1.5)**: Native integration of the `<video:requires_subscription>` tag to signal premium paywall barriers or free-tier states, preventing user-frustration search algorithmic penalties.
18
18
  - **Video Country & Device Restrictions (v1.1.4)**: Advanced access control policy injection via `<video:restriction>` and `<video:platform>` properties to strictly control video delivery layouts across global boundaries and distinct screen classes (`web`, `mobile`, `tv`).
19
+ - **Video Engagement Metrics & Validation (v1.1.3)**: Native integration of `<video:duration>` and `<video:view_count>` statistical metrics featuring deterministic float truncation (`Math.floor`) and strict bounding boundaries (0 to 28,800 seconds max).
19
20
  - **Google Video Live Streaming (v1.1.1)**: Native injection of the `<video:live>` parameter to flag active real-time broadcasts and instantly trigger red **LIVE** badges on Google SERP matrices.
20
21
  - **Google News Support**: Instant discovery for news publications with strict support for required news name, language tag, and publication date attributes.
21
22
  - **Internationalization (Hreflang)**: Seamless rendering of `xhtml:link` relation tags to govern multi-regional and multilingual indexing across global markets.
@@ -65,6 +66,8 @@ export async function GET() {
65
66
  publication_date: new Date(),
66
67
  duration: 7200,
67
68
  view_count: 25000,
69
+ // v1.1.5: Flexible Paywall Registration (Accepts boolean or strict 'yes' | 'no')
70
+ requires_subscription: true,
68
71
  // v1.1.4: Strict Geographic Filtering & Capitalization Sanitization
69
72
  restriction: {
70
73
  relationship: 'allow',
@@ -84,7 +87,7 @@ export async function GET() {
84
87
  images: [
85
88
  {
86
89
  loc: 'https://fomadev.com/images/product.png',
87
- title: ' Premium Wireless Keyboard ', // v1.1.2: Auto-trimmed preventively
90
+ title: ' Premium Wireless Keyboard ', // v1.1.2: Auto-trimmed preventively
88
91
  caption: 'Close-up shot of our custom mechanical keyboard layout with XML characters like & or <', // v1.1.2: Deep XML Escaping
89
92
  geo_location: 'Kinshasa, Democratic Republic of the Congo', // v1.1.0 Local SEO
90
93
  license: 'https://fomadev.com/terms/licensing' // v1.1.0 Badging
@@ -279,6 +282,15 @@ Generates a standard Next.js `Response` object with the correct `application/xml
279
282
 
280
283
  ## Technical Implementation
281
284
 
285
+ ### Paywall Registration & Subscription Guardrails (v1.1.5)
286
+ For media syndicates, educational organizations, and video streaming architectures utilizing monetization paywalls, misconfiguring premium access markers can lead to harsh ranking reductions due to misleading click funnels (user frustration loops). **v1.1.5** abstracts this integration completely:
287
+
288
+ - **Polymorphic Flag Binding**: Developers can feed standard TypeScript boolean primitives (`true`/`false`) smoothly during layout binding, or explicitly pass native schema tokens (`'yes'` / `'no'`).
289
+
290
+ - **Data Normalization Engine**: The compiler captures boolean states and automatically renders them into standard Googlebot-compliant entity wrappers behind the scenes.
291
+
292
+ - **Fail-Fast Boundary Validation**: Inputting mixed type variables instantly triggers an architectural parsing error at runtime to halt invalid XML distribution formats before deployment.
293
+
282
294
  ### Video Distribution Rights & Geo-Blocking Safeguards (v1.1.4)
283
295
  For streaming platforms, modern SaaS corporations, and decentralized content houses, geoblocking and device-specific index filtering are critical mechanisms needed to comply with broadcasting licenses and localized compliance laws. **v1.1.4** delivers high-performance runtime guardrails enforcing the exact schemas expected by Googlebot:
284
296
 
package/dist/index.cjs CHANGED
@@ -45,7 +45,7 @@ function escapeXml(unsafe) {
45
45
  });
46
46
  }
47
47
 
48
- // src/core/generator.ts
48
+ // src/core/builders/url-builder.ts
49
49
  function sanitizeAndValidateUrl(rawUrl, context) {
50
50
  const url = rawUrl ? rawUrl.trim() : "";
51
51
  if (!url.startsWith("http://") && !url.startsWith("https://")) {
@@ -76,192 +76,231 @@ function sanitizeAndValidateUrl(rawUrl, context) {
76
76
  }
77
77
  return url;
78
78
  }
79
- function generateXml(entries, options = {}) {
80
- const now = (/* @__PURE__ */ new Date()).toISOString();
81
- let finalEntries = [...entries];
82
- if (options.sortByPriority) {
83
- finalEntries.sort((a, b) => {
84
- const priorityA = a.priority !== void 0 ? a.priority : 0.5;
85
- const priorityB = b.priority !== void 0 ? b.priority : 0.5;
86
- return priorityB - priorityA;
87
- });
88
- }
89
- let xml = `<?xml version="1.0" encoding="UTF-8"?>
79
+ function buildUrlBaseXml(entry, options, nowIso) {
80
+ let xml = "";
81
+ const cleanMainUrl = sanitizeAndValidateUrl(entry.url, "main entry");
82
+ xml += ` <loc>${escapeXml(cleanMainUrl)}</loc>
90
83
  `;
91
- xml += `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
92
- `;
93
- xml += ` xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"
84
+ if (entry.alternates?.length) {
85
+ for (const alt of entry.alternates) {
86
+ const cleanAltUrl = sanitizeAndValidateUrl(alt.href, "alternate link");
87
+ xml += ` <xhtml:link rel="alternate" hreflang="${escapeXml(alt.hreflang)}" href="${escapeXml(cleanAltUrl)}" />
94
88
  `;
95
- xml += ` xmlns:video="http://www.google.com/schemas/sitemap-video/1.1"
89
+ }
90
+ }
91
+ let lastmodValue = entry.lastmod;
92
+ if (options.autoLastmod && !lastmodValue) {
93
+ lastmodValue = nowIso;
94
+ }
95
+ if (lastmodValue) {
96
+ const date = lastmodValue instanceof Date ? lastmodValue.toISOString() : lastmodValue;
97
+ xml += ` <lastmod>${date}</lastmod>
96
98
  `;
97
- xml += ` xmlns:news="http://www.google.com/schemas/sitemap-news/0.9"
99
+ }
100
+ if (entry.changefreq) {
101
+ xml += ` <changefreq>${entry.changefreq}</changefreq>
98
102
  `;
99
- xml += ` xmlns:xhtml="http://www.w3.org/1999/xhtml">
103
+ }
104
+ if (entry.priority !== void 0) {
105
+ xml += ` <priority>${entry.priority.toFixed(1)}</priority>
100
106
  `;
101
- for (const entry of finalEntries) {
102
- const cleanMainUrl = sanitizeAndValidateUrl(entry.url, "main entry");
103
- xml += ` <url>
107
+ }
108
+ return xml;
109
+ }
110
+
111
+ // src/core/builders/image-builder.ts
112
+ function buildImageXml(images) {
113
+ if (!images?.length) return "";
114
+ let xml = "";
115
+ for (const img of images) {
116
+ const cleanImgUrl = sanitizeAndValidateUrl(img.loc, "image location");
117
+ xml += ` <image:image>
104
118
  `;
105
- xml += ` <loc>${escapeXml(cleanMainUrl)}</loc>
119
+ xml += ` <image:loc>${escapeXml(cleanImgUrl)}</image:loc>
106
120
  `;
107
- if (entry.alternates?.length) {
108
- for (const alt of entry.alternates) {
109
- const cleanAltUrl = sanitizeAndValidateUrl(alt.href, "alternate link");
110
- xml += ` <xhtml:link rel="alternate" hreflang="${escapeXml(alt.hreflang)}" href="${escapeXml(cleanAltUrl)}" />
121
+ if (img.title && img.title.trim() !== "") {
122
+ xml += ` <image:title>${escapeXml(img.title.trim())}</image:title>
111
123
  `;
112
- }
113
- }
114
- let lastmodValue = entry.lastmod;
115
- if (options.autoLastmod && !lastmodValue) {
116
- lastmodValue = now;
117
124
  }
118
- if (lastmodValue) {
119
- const date = lastmodValue instanceof Date ? lastmodValue.toISOString() : lastmodValue;
120
- xml += ` <lastmod>${date}</lastmod>
125
+ if (img.caption && img.caption.trim() !== "") {
126
+ xml += ` <image:caption>${escapeXml(img.caption.trim())}</image:caption>
121
127
  `;
122
128
  }
123
- if (entry.changefreq) {
124
- xml += ` <changefreq>${entry.changefreq}</changefreq>
129
+ if (img.geo_location && img.geo_location.trim() !== "") {
130
+ xml += ` <image:geo_location>${escapeXml(img.geo_location.trim())}</image:geo_location>
125
131
  `;
126
132
  }
127
- if (entry.priority !== void 0) {
128
- xml += ` <priority>${entry.priority.toFixed(1)}</priority>
133
+ if (img.license) {
134
+ const cleanLicenseUrl = sanitizeAndValidateUrl(img.license, "image license URL");
135
+ xml += ` <image:license>${escapeXml(cleanLicenseUrl)}</image:license>
129
136
  `;
130
137
  }
131
- if (entry.images?.length) {
132
- for (const img of entry.images) {
133
- const cleanImgUrl = sanitizeAndValidateUrl(img.loc, "image location");
134
- xml += ` <image:image>
138
+ xml += ` </image:image>
139
+ `;
140
+ }
141
+ return xml;
142
+ }
143
+
144
+ // src/core/builders/video-builder.ts
145
+ function buildVideoXml(videos) {
146
+ if (!videos?.length) return "";
147
+ let xml = "";
148
+ for (const vid of videos) {
149
+ const cleanThumbLoc = sanitizeAndValidateUrl(vid.thumbnail_loc, "video thumbnail");
150
+ const cleanContentLoc = vid.content_loc ? sanitizeAndValidateUrl(vid.content_loc, "video content location") : void 0;
151
+ const cleanPlayerLoc = vid.player_loc ? sanitizeAndValidateUrl(vid.player_loc, "video player location") : void 0;
152
+ xml += ` <video:video>
135
153
  `;
136
- xml += ` <image:loc>${escapeXml(cleanImgUrl)}</image:loc>
154
+ xml += ` <video:thumbnail_loc>${escapeXml(cleanThumbLoc)}</video:thumbnail_loc>
137
155
  `;
138
- if (img.title && img.title.trim() !== "") {
139
- xml += ` <image:title>${escapeXml(img.title.trim())}</image:title>
156
+ xml += ` <video:title>${escapeXml(vid.title)}</video:title>
140
157
  `;
141
- }
142
- if (img.caption && img.caption.trim() !== "") {
143
- xml += ` <image:caption>${escapeXml(img.caption.trim())}</image:caption>
158
+ xml += ` <video:description>${escapeXml(vid.description)}</video:description>
144
159
  `;
145
- }
146
- if (img.geo_location && img.geo_location.trim() !== "") {
147
- xml += ` <image:geo_location>${escapeXml(img.geo_location.trim())}</image:geo_location>
160
+ if (cleanContentLoc) xml += ` <video:content_loc>${escapeXml(cleanContentLoc)}</video:content_loc>
148
161
  `;
149
- }
150
- if (img.license) {
151
- const cleanLicenseUrl = sanitizeAndValidateUrl(img.license, "image license URL");
152
- xml += ` <image:license>${escapeXml(cleanLicenseUrl)}</image:license>
162
+ if (cleanPlayerLoc) xml += ` <video:player_loc>${escapeXml(cleanPlayerLoc)}</video:player_loc>
153
163
  `;
154
- }
155
- xml += ` </image:image>
164
+ if (vid.publication_date) {
165
+ const vDate = vid.publication_date instanceof Date ? vid.publication_date.toISOString() : vid.publication_date;
166
+ xml += ` <video:publication_date>${vDate}</video:publication_date>
156
167
  `;
168
+ }
169
+ if (vid.duration !== void 0) {
170
+ const finalDuration = Math.floor(vid.duration);
171
+ if (finalDuration < 0 || finalDuration > 28800) {
172
+ throw new Error(
173
+ `[next-advanced-sitemap] Invalid video duration: ${finalDuration}. Duration must be an integer between 0 and 28800 seconds (8 hours).`
174
+ );
157
175
  }
176
+ xml += ` <video:duration>${finalDuration}</video:duration>
177
+ `;
158
178
  }
159
- if (entry.videos?.length) {
160
- for (const vid of entry.videos) {
161
- const cleanThumbLoc = sanitizeAndValidateUrl(vid.thumbnail_loc, "video thumbnail");
162
- const cleanContentLoc = vid.content_loc ? sanitizeAndValidateUrl(vid.content_loc, "video content location") : void 0;
163
- const cleanPlayerLoc = vid.player_loc ? sanitizeAndValidateUrl(vid.player_loc, "video player location") : void 0;
164
- xml += ` <video:video>
179
+ if (vid.view_count !== void 0) {
180
+ const finalViewCount = Math.floor(vid.view_count);
181
+ if (finalViewCount < 0) {
182
+ throw new Error(
183
+ `[next-advanced-sitemap] Invalid video view_count: ${finalViewCount}. View count cannot be negative.`
184
+ );
185
+ }
186
+ xml += ` <video:view_count>${finalViewCount}</video:view_count>
165
187
  `;
166
- xml += ` <video:thumbnail_loc>${escapeXml(cleanThumbLoc)}</video:thumbnail_loc>
188
+ }
189
+ if (vid.live) {
190
+ xml += ` <video:live>${vid.live}</video:live>
167
191
  `;
168
- xml += ` <video:title>${escapeXml(vid.title)}</video:title>
192
+ }
193
+ if (vid.restriction) {
194
+ if (!vid.restriction.countries || vid.restriction.countries.length === 0) {
195
+ throw new Error(
196
+ `[next-advanced-sitemap] Invalid video restriction: countries array cannot be empty.`
197
+ );
198
+ }
199
+ const cleanCountries = vid.restriction.countries.map((country) => {
200
+ const code = country.trim().toUpperCase();
201
+ if (code.length < 2 || code.length > 3) {
202
+ throw new Error(
203
+ `[next-advanced-sitemap] Invalid ISO country code detected: "${country}". Must be a valid ISO 3166 code.`
204
+ );
205
+ }
206
+ return code;
207
+ });
208
+ const countriesStr = cleanCountries.join(" ");
209
+ xml += ` <video:restriction relationship="${vid.restriction.relationship}">${countriesStr}</video:restriction>
169
210
  `;
170
- xml += ` <video:description>${escapeXml(vid.description)}</video:description>
211
+ }
212
+ if (vid.platform) {
213
+ if (!vid.platform.platforms || vid.platform.platforms.length === 0) {
214
+ throw new Error(
215
+ `[next-advanced-sitemap] Invalid video platform: platforms array cannot be empty.`
216
+ );
217
+ }
218
+ const validPlatforms = ["web", "mobile", "tv"];
219
+ for (const p of vid.platform.platforms) {
220
+ if (!validPlatforms.includes(p)) {
221
+ throw new Error(
222
+ `[next-advanced-sitemap] Invalid platform type: "${p}". Allowed values are 'web', 'mobile', or 'tv'.`
223
+ );
224
+ }
225
+ }
226
+ const platformsStr = vid.platform.platforms.join(" ");
227
+ xml += ` <video:platform relationship="${vid.platform.relationship}">${platformsStr}</video:platform>
171
228
  `;
172
- if (cleanContentLoc) xml += ` <video:content_loc>${escapeXml(cleanContentLoc)}</video:content_loc>
229
+ }
230
+ if (vid.requires_subscription !== void 0) {
231
+ let subValue;
232
+ if (typeof vid.requires_subscription === "boolean") {
233
+ subValue = vid.requires_subscription ? "yes" : "no";
234
+ } else if (vid.requires_subscription === "yes" || vid.requires_subscription === "no") {
235
+ subValue = vid.requires_subscription;
236
+ } else {
237
+ throw new Error(
238
+ `[next-advanced-sitemap] Invalid value for requires_subscription: "${vid.requires_subscription}". Expected boolean or strict string 'yes' | 'no'.`
239
+ );
240
+ }
241
+ xml += ` <video:requires_subscription>${subValue}</video:requires_subscription>
173
242
  `;
174
- if (cleanPlayerLoc) xml += ` <video:player_loc>${escapeXml(cleanPlayerLoc)}</video:player_loc>
243
+ }
244
+ xml += ` </video:video>
175
245
  `;
176
- if (vid.publication_date) {
177
- const vDate = vid.publication_date instanceof Date ? vid.publication_date.toISOString() : vid.publication_date;
178
- xml += ` <video:publication_date>${vDate}</video:publication_date>
246
+ }
247
+ return xml;
248
+ }
249
+
250
+ // src/core/builders/news-builder.ts
251
+ function buildNewsXml(news) {
252
+ if (!news) return "";
253
+ const nDate = news.publication_date instanceof Date ? news.publication_date.toISOString() : news.publication_date;
254
+ let xml = "";
255
+ xml += ` <news:news>
179
256
  `;
180
- }
181
- if (vid.duration !== void 0) {
182
- const finalDuration = Math.floor(vid.duration);
183
- if (finalDuration < 0 || finalDuration > 28800) {
184
- throw new Error(
185
- `[next-advanced-sitemap] Invalid video duration: ${finalDuration}. Duration must be an integer between 0 and 28800 seconds (8 hours).`
186
- );
187
- }
188
- xml += ` <video:duration>${finalDuration}</video:duration>
257
+ xml += ` <news:publication>
189
258
  `;
190
- }
191
- if (vid.view_count !== void 0) {
192
- const finalViewCount = Math.floor(vid.view_count);
193
- if (finalViewCount < 0) {
194
- throw new Error(
195
- `[next-advanced-sitemap] Invalid video view_count: ${finalViewCount}. View count cannot be negative.`
196
- );
197
- }
198
- xml += ` <video:view_count>${finalViewCount}</video:view_count>
259
+ xml += ` <news:name>${escapeXml(news.name)}</news:name>
199
260
  `;
200
- }
201
- if (vid.live) {
202
- xml += ` <video:live>${vid.live}</video:live>
261
+ xml += ` <news:language>${escapeXml(news.language)}</news:language>
203
262
  `;
204
- }
205
- if (vid.restriction) {
206
- if (!vid.restriction.countries || vid.restriction.countries.length === 0) {
207
- throw new Error(
208
- `[next-advanced-sitemap] Invalid video restriction: countries array cannot be empty.`
209
- );
210
- }
211
- const cleanCountries = vid.restriction.countries.map((country) => {
212
- const code = country.trim().toUpperCase();
213
- if (code.length < 2 || code.length > 3) {
214
- throw new Error(
215
- `[next-advanced-sitemap] Invalid ISO country code detected: "${country}". Must be a valid ISO 3166 code.`
216
- );
217
- }
218
- return code;
219
- });
220
- const countriesStr = cleanCountries.join(" ");
221
- xml += ` <video:restriction relationship="${vid.restriction.relationship}">${countriesStr}</video:restriction>
263
+ xml += ` </news:publication>
222
264
  `;
223
- }
224
- if (vid.platform) {
225
- if (!vid.platform.platforms || vid.platform.platforms.length === 0) {
226
- throw new Error(
227
- `[next-advanced-sitemap] Invalid video platform: platforms array cannot be empty.`
228
- );
229
- }
230
- const validPlatforms = ["web", "mobile", "tv"];
231
- for (const p of vid.platform.platforms) {
232
- if (!validPlatforms.includes(p)) {
233
- throw new Error(
234
- `[next-advanced-sitemap] Invalid platform type: "${p}". Allowed values are 'web', 'mobile', or 'tv'.`
235
- );
236
- }
237
- }
238
- const platformsStr = vid.platform.platforms.join(" ");
239
- xml += ` <video:platform relationship="${vid.platform.relationship}">${platformsStr}</video:platform>
265
+ xml += ` <news:publication_date>${nDate}</news:publication_date>
240
266
  `;
241
- }
242
- xml += ` </video:video>
267
+ xml += ` <news:title>${escapeXml(news.title)}</news:title>
243
268
  `;
244
- }
245
- }
246
- if (entry.news) {
247
- const nDate = entry.news.publication_date instanceof Date ? entry.news.publication_date.toISOString() : entry.news.publication_date;
248
- xml += ` <news:news>
269
+ xml += ` </news:news>
249
270
  `;
250
- xml += ` <news:publication>
271
+ return xml;
272
+ }
273
+
274
+ // src/core/generator.ts
275
+ function generateXml(entries, options = {}) {
276
+ const now = (/* @__PURE__ */ new Date()).toISOString();
277
+ let finalEntries = [...entries];
278
+ if (options.sortByPriority) {
279
+ finalEntries.sort((a, b) => {
280
+ const priorityA = a.priority !== void 0 ? a.priority : 0.5;
281
+ const priorityB = b.priority !== void 0 ? b.priority : 0.5;
282
+ return priorityB - priorityA;
283
+ });
284
+ }
285
+ let xml = `<?xml version="1.0" encoding="UTF-8"?>
251
286
  `;
252
- xml += ` <news:name>${escapeXml(entry.news.name)}</news:name>
287
+ xml += `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
253
288
  `;
254
- xml += ` <news:language>${escapeXml(entry.news.language)}</news:language>
289
+ xml += ` xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"
255
290
  `;
256
- xml += ` </news:publication>
291
+ xml += ` xmlns:video="http://www.google.com/schemas/sitemap-video/1.1"
257
292
  `;
258
- xml += ` <news:publication_date>${nDate}</news:publication_date>
293
+ xml += ` xmlns:news="http://www.google.com/schemas/sitemap-news/0.9"
259
294
  `;
260
- xml += ` <news:title>${escapeXml(entry.news.title)}</news:title>
295
+ xml += ` xmlns:xhtml="http://www.w3.org/1999/xhtml">
261
296
  `;
262
- xml += ` </news:news>
297
+ for (const entry of finalEntries) {
298
+ xml += ` <url>
263
299
  `;
264
- }
300
+ xml += buildUrlBaseXml(entry, options, now);
301
+ xml += buildImageXml(entry.images);
302
+ xml += buildVideoXml(entry.videos);
303
+ xml += buildNewsXml(entry.news);
265
304
  xml += ` </url>
266
305
  `;
267
306
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/utils/xml-escape.ts","../src/core/generator.ts"],"sourcesContent":["/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry, SitemapOptions } from './types/sitemap.js';\nimport { generateXml } from './core/generator.js';\n\nexport * from './types/sitemap.js';\n\n/**\n * Génère une réponse HTTP compatible Next.js (App Router) avec options de configuration.\n * v1.0.9 : Injection dynamique et personnalisable de l'en-tête Cache-Control via l'option maxAge\n * * @param entries - Liste des entrées du sitemap\n * @param options - Options de génération et de mise en cache (ex: autoLastmod, maxAge)\n * @returns Une instance de Response contenant le flux XML configuré\n */\nexport function getServerSitemapResponse(\n entries: SitemapEntry[], \n options: SitemapOptions = {}\n): Response {\n const xml = generateXml(entries, options);\n\n // Détermination de la stratégie de mise en cache (v1.0.9)\n const cacheControlHeader = options.maxAge !== undefined\n ? `public, max-age=${options.maxAge}, must-revalidate`\n : 'public, s-maxage=86400, stale-while-revalidate';\n\n return new Response(xml, {\n headers: {\n 'Content-Type': 'application/xml',\n 'Cache-Control': cacheControlHeader,\n },\n });\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\n/**\n * Convertit les caractères spéciaux en entités XML pour éviter la corruption du fichier.\n * Gère : <, >, &, \", '\n */\nexport function escapeXml(unsafe: string | undefined | null): string {\n if (!unsafe) return '';\n \n return unsafe.replace(/[<>&\"']/g, (c) => {\n switch (c) {\n case '<': return '&lt;';\n case '>': return '&gt;';\n case '&': return '&amp;';\n case '\"': return '&quot;';\n case \"'\": return '&apos;';\n default: return c;\n }\n });\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry, SitemapOptions } from '../types/sitemap.js';\nimport { escapeXml } from '../utils/xml-escape.js';\n\n/**\n * Nettoie et valide de manière stricte le format et la structure d'une URL.\n * v1.0.7 : Intégration de l'Auto-Trimming (nettoyage des espaces de début et de fin)\n */\nfunction 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 flux XML complet du sitemap incluant les extensions Images, Vidéos, News et Hreflang.\n * v1.1.4 : Prise en charge et validation stricte des restrictions pays (ISO 3166) et plateformes (web, mobile, tv)\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 const cleanMainUrl = sanitizeAndValidateUrl(entry.url, 'main entry');\n\n xml += ` <url>\\n`;\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 = now;\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 // Extension Images\n if (entry.images?.length) {\n for (const img of entry.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\n // Extension Vidéos\n if (entry.videos?.length) {\n for (const vid of entry.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 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 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 xml += ` </video:video>\\n`;\n }\n }\n\n // Extension News\n if (entry.news) {\n const nDate = entry.news.publication_date instanceof Date ? entry.news.publication_date.toISOString() : entry.news.publication_date;\n xml += ` <news:news>\\n`;\n xml += ` <news:publication>\\n`;\n xml += ` <news:name>${escapeXml(entry.news.name)}</news:name>\\n`;\n xml += ` <news:language>${escapeXml(entry.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(entry.news.title)}</news:title>\\n`;\n xml += ` </news:news>\\n`;\n }\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/generator.ts"],"sourcesContent":["/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry, SitemapOptions } from './types/sitemap.js';\nimport { generateXml } from './core/generator.js';\n\nexport * from './types/sitemap.js';\n\n/**\n * Génère une réponse HTTP compatible Next.js (App Router) avec options de configuration.\n * v1.0.9 : Injection dynamique et personnalisable de l'en-tête Cache-Control via l'option maxAge\n * * @param entries - Liste des entrées du sitemap\n * @param options - Options de génération et de mise en cache (ex: autoLastmod, maxAge)\n * @returns Une instance de Response contenant le flux XML configuré\n */\nexport function getServerSitemapResponse(\n entries: SitemapEntry[], \n options: SitemapOptions = {}\n): Response {\n const xml = generateXml(entries, options);\n\n // Détermination de la stratégie de mise en cache (v1.0.9)\n const cacheControlHeader = options.maxAge !== undefined\n ? `public, max-age=${options.maxAge}, must-revalidate`\n : 'public, s-maxage=86400, stale-while-revalidate';\n\n return new Response(xml, {\n headers: {\n 'Content-Type': 'application/xml',\n 'Cache-Control': cacheControlHeader,\n },\n });\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\n/**\n * Convertit les caractères spéciaux en entités XML pour éviter la corruption du fichier.\n * Gère : <, >, &, \", '\n */\nexport function escapeXml(unsafe: string | undefined | null): string {\n if (!unsafe) return '';\n \n return unsafe.replace(/[<>&\"']/g, (c) => {\n switch (c) {\n case '<': return '&lt;';\n case '>': return '&gt;';\n case '&': return '&amp;';\n case '\"': return '&quot;';\n case \"'\": return '&apos;';\n default: return c;\n }\n });\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry, SitemapOptions } from '../../types/sitemap.js';\nimport { escapeXml } from '../../utils/xml-escape.js';\n\n/**\n * Nettoie et valide de manière stricte le format et la structure d'une URL.\n * v1.0.7 : Intégration de l'Auto-Trimming (nettoyage des espaces de début et de fin)\n */\nexport function sanitizeAndValidateUrl(rawUrl: string, context: string): string {\n const url = rawUrl ? rawUrl.trim() : '';\n\n if (!url.startsWith('http://') && !url.startsWith('https://')) {\n throw new Error(\n `[next-advanced-sitemap] Invalid URL in ${context}: \"${url}\". URLs must start with http:// or https://`\n );\n }\n\n if (url.includes(' ')) {\n throw new Error(\n `[next-advanced-sitemap] Malformed URL structure detected in ${context}: \"${url}\". Please verify spaces or special characters.`\n );\n }\n\n let isValid = false;\n if (typeof URL.canParse === 'function') {\n isValid = URL.canParse(url);\n } else {\n try {\n new URL(url);\n isValid = true;\n } catch {\n isValid = false;\n }\n }\n\n if (!isValid) {\n throw new Error(\n `[next-advanced-sitemap] Malformed URL structure detected in ${context}: \"${url}\". Please verify spaces or special characters.`\n );\n }\n\n return url;\n}\n\n/**\n * Génère le bloc XML de base pour un nœud URL (loc, alternates, lastmod, changefreq, priority).\n */\nexport function buildUrlBaseXml(entry: SitemapEntry, options: SitemapOptions, nowIso: string): string {\n let xml = '';\n \n const cleanMainUrl = sanitizeAndValidateUrl(entry.url, 'main entry');\n xml += ` <loc>${escapeXml(cleanMainUrl)}</loc>\\n`;\n\n if (entry.alternates?.length) {\n for (const alt of entry.alternates) {\n const cleanAltUrl = sanitizeAndValidateUrl(alt.href, 'alternate link');\n xml += ` <xhtml:link rel=\"alternate\" hreflang=\"${escapeXml(alt.hreflang)}\" href=\"${escapeXml(cleanAltUrl)}\" />\\n`;\n }\n }\n\n let lastmodValue = entry.lastmod;\n if (options.autoLastmod && !lastmodValue) {\n lastmodValue = nowIso;\n }\n\n if (lastmodValue) {\n const date = lastmodValue instanceof Date ? lastmodValue.toISOString() : lastmodValue;\n xml += ` <lastmod>${date}</lastmod>\\n`;\n }\n\n if (entry.changefreq) {\n xml += ` <changefreq>${entry.changefreq}</changefreq>\\n`;\n }\n\n if (entry.priority !== undefined) {\n xml += ` <priority>${(entry.priority as number).toFixed(1)}</priority>\\n`;\n }\n\n return xml;\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry } from '../../types/sitemap.js';\nimport { escapeXml } from '../../utils/xml-escape.js';\nimport { sanitizeAndValidateUrl } from './url-builder.js';\n\nexport function buildImageXml(images: SitemapEntry['images']): string {\n if (!images?.length) return '';\n\n let xml = '';\n for (const img of images) {\n const cleanImgUrl = sanitizeAndValidateUrl(img.loc, 'image location');\n \n xml += ` <image:image>\\n`;\n xml += ` <image:loc>${escapeXml(cleanImgUrl)}</image:loc>\\n`;\n \n if (img.title && img.title.trim() !== '') {\n xml += ` <image:title>${escapeXml(img.title.trim())}</image:title>\\n`;\n }\n if (img.caption && img.caption.trim() !== '') {\n xml += ` <image:caption>${escapeXml(img.caption.trim())}</image:caption>\\n`;\n }\n \n if (img.geo_location && img.geo_location.trim() !== '') {\n xml += ` <image:geo_location>${escapeXml(img.geo_location.trim())}</image:geo_location>\\n`;\n }\n \n if (img.license) {\n const cleanLicenseUrl = sanitizeAndValidateUrl(img.license, 'image license URL');\n xml += ` <image:license>${escapeXml(cleanLicenseUrl)}</image:license>\\n`;\n }\n \n xml += ` </image:image>\\n`;\n }\n \n return xml;\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry } from '../../types/sitemap.js';\nimport { escapeXml } from '../../utils/xml-escape.js';\nimport { sanitizeAndValidateUrl } from './url-builder.js';\n\nexport function buildVideoXml(videos: SitemapEntry['videos']): string {\n if (!videos?.length) return '';\n\n let xml = '';\n for (const vid of videos) {\n const cleanThumbLoc = sanitizeAndValidateUrl(vid.thumbnail_loc, 'video thumbnail');\n const cleanContentLoc = vid.content_loc ? sanitizeAndValidateUrl(vid.content_loc, 'video content location') : undefined;\n const cleanPlayerLoc = vid.player_loc ? sanitizeAndValidateUrl(vid.player_loc, 'video player location') : undefined;\n\n xml += ` <video:video>\\n`;\n xml += ` <video:thumbnail_loc>${escapeXml(cleanThumbLoc)}</video:thumbnail_loc>\\n`;\n xml += ` <video:title>${escapeXml(vid.title)}</video:title>\\n`;\n xml += ` <video:description>${escapeXml(vid.description)}</video:description>\\n`;\n \n if (cleanContentLoc) xml += ` <video:content_loc>${escapeXml(cleanContentLoc)}</video:content_loc>\\n`;\n if (cleanPlayerLoc) xml += ` <video:player_loc>${escapeXml(cleanPlayerLoc)}</video:player_loc>\\n`;\n \n if (vid.publication_date) {\n const vDate = vid.publication_date instanceof Date ? vid.publication_date.toISOString() : vid.publication_date;\n xml += ` <video:publication_date>${vDate}</video:publication_date>\\n`;\n }\n\n // ✨ Validation et Sérialisation de la durée (0 - 28800s)\n if (vid.duration !== undefined) {\n const finalDuration = Math.floor(vid.duration);\n if (finalDuration < 0 || finalDuration > 28800) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video duration: ${finalDuration}. Duration must be an integer between 0 and 28800 seconds (8 hours).`\n );\n }\n xml += ` <video:duration>${finalDuration}</video:duration>\\n`;\n }\n\n // ✨ Validation et Sérialisation du nombre de vues (>= 0)\n if (vid.view_count !== undefined) {\n const finalViewCount = Math.floor(vid.view_count);\n if (finalViewCount < 0) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video view_count: ${finalViewCount}. View count cannot be negative.`\n );\n }\n xml += ` <video:view_count>${finalViewCount}</video:view_count>\\n`;\n }\n\n if (vid.live) {\n xml += ` <video:live>${vid.live}</video:live>\\n`;\n }\n\n // ✨ Validation et Sérialisation des Restrictions Pays (v1.1.4)\n if (vid.restriction) {\n if (!vid.restriction.countries || vid.restriction.countries.length === 0) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video restriction: countries array cannot be empty.`\n );\n }\n\n const cleanCountries = vid.restriction.countries.map(country => {\n const code = country.trim().toUpperCase();\n if (code.length < 2 || code.length > 3) {\n throw new Error(\n `[next-advanced-sitemap] Invalid ISO country code detected: \"${country}\". Must be a valid ISO 3166 code.`\n );\n }\n return code;\n });\n\n const countriesStr = cleanCountries.join(' ');\n xml += ` <video:restriction relationship=\"${vid.restriction.relationship}\">${countriesStr}</video:restriction>\\n`;\n }\n\n // ✨ Validation et Sérialisation des Plateformes (v1.1.4)\n if (vid.platform) {\n if (!vid.platform.platforms || vid.platform.platforms.length === 0) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video platform: platforms array cannot be empty.`\n );\n }\n\n const validPlatforms = ['web', 'mobile', 'tv'];\n for (const p of vid.platform.platforms) {\n if (!validPlatforms.includes(p)) {\n throw new Error(\n `[next-advanced-sitemap] Invalid platform type: \"${p}\". Allowed values are 'web', 'mobile', or 'tv'.`\n );\n }\n }\n\n const platformsStr = vid.platform.platforms.join(' ');\n xml += ` <video:platform relationship=\"${vid.platform.relationship}\">${platformsStr}</video:platform>\\n`;\n }\n \n if (vid.requires_subscription !== undefined) {\n let subValue: 'yes' | 'no';\n\n if (typeof vid.requires_subscription === 'boolean') {\n subValue = vid.requires_subscription ? 'yes' : 'no';\n } else if (vid.requires_subscription === 'yes' || vid.requires_subscription === 'no') {\n subValue = vid.requires_subscription;\n } else {\n throw new Error(\n `[next-advanced-sitemap] Invalid value for requires_subscription: \"${vid.requires_subscription}\". Expected boolean or strict string 'yes' | 'no'.`\n );\n }\n\n xml += ` <video:requires_subscription>${subValue}</video:requires_subscription>\\n`;\n }\n\n 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 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, 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';\n\n/**\n * Génère le flux XML complet du sitemap incluant les extensions Images, Vidéos, News et Hreflang.\n * v1.1.4 : Version découplée et hautement modulaire.\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 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":[]}
package/dist/index.d.cts CHANGED
@@ -67,6 +67,12 @@ interface SitemapVideo {
67
67
  restriction?: VideoRestriction;
68
68
  /** (Optional) v1.1.4: Restriction selon le type d'appareil / plateforme. */
69
69
  platform?: VideoPlatform;
70
+ /**
71
+ * v1.1.5 : Indique si l'accès à la vidéo nécessite un abonnement payant.
72
+ * Accepte true/false ou de manière stricte 'yes'/'no'.
73
+ * @see https://developers.google.com/search/docs/crawling-indexing/sitemaps/video-sitemaps
74
+ */
75
+ requires_subscription?: boolean | 'yes' | 'no';
70
76
  }
71
77
  /**
72
78
  * Interface pour Google News
package/dist/index.d.ts CHANGED
@@ -67,6 +67,12 @@ interface SitemapVideo {
67
67
  restriction?: VideoRestriction;
68
68
  /** (Optional) v1.1.4: Restriction selon le type d'appareil / plateforme. */
69
69
  platform?: VideoPlatform;
70
+ /**
71
+ * v1.1.5 : Indique si l'accès à la vidéo nécessite un abonnement payant.
72
+ * Accepte true/false ou de manière stricte 'yes'/'no'.
73
+ * @see https://developers.google.com/search/docs/crawling-indexing/sitemaps/video-sitemaps
74
+ */
75
+ requires_subscription?: boolean | 'yes' | 'no';
70
76
  }
71
77
  /**
72
78
  * Interface pour Google News
package/dist/index.js CHANGED
@@ -19,7 +19,7 @@ function escapeXml(unsafe) {
19
19
  });
20
20
  }
21
21
 
22
- // src/core/generator.ts
22
+ // src/core/builders/url-builder.ts
23
23
  function sanitizeAndValidateUrl(rawUrl, context) {
24
24
  const url = rawUrl ? rawUrl.trim() : "";
25
25
  if (!url.startsWith("http://") && !url.startsWith("https://")) {
@@ -50,192 +50,231 @@ function sanitizeAndValidateUrl(rawUrl, context) {
50
50
  }
51
51
  return url;
52
52
  }
53
- function generateXml(entries, options = {}) {
54
- const now = (/* @__PURE__ */ new Date()).toISOString();
55
- let finalEntries = [...entries];
56
- if (options.sortByPriority) {
57
- finalEntries.sort((a, b) => {
58
- const priorityA = a.priority !== void 0 ? a.priority : 0.5;
59
- const priorityB = b.priority !== void 0 ? b.priority : 0.5;
60
- return priorityB - priorityA;
61
- });
62
- }
63
- let xml = `<?xml version="1.0" encoding="UTF-8"?>
53
+ function buildUrlBaseXml(entry, options, nowIso) {
54
+ let xml = "";
55
+ const cleanMainUrl = sanitizeAndValidateUrl(entry.url, "main entry");
56
+ xml += ` <loc>${escapeXml(cleanMainUrl)}</loc>
64
57
  `;
65
- xml += `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
66
- `;
67
- xml += ` xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"
58
+ if (entry.alternates?.length) {
59
+ for (const alt of entry.alternates) {
60
+ const cleanAltUrl = sanitizeAndValidateUrl(alt.href, "alternate link");
61
+ xml += ` <xhtml:link rel="alternate" hreflang="${escapeXml(alt.hreflang)}" href="${escapeXml(cleanAltUrl)}" />
68
62
  `;
69
- xml += ` xmlns:video="http://www.google.com/schemas/sitemap-video/1.1"
63
+ }
64
+ }
65
+ let lastmodValue = entry.lastmod;
66
+ if (options.autoLastmod && !lastmodValue) {
67
+ lastmodValue = nowIso;
68
+ }
69
+ if (lastmodValue) {
70
+ const date = lastmodValue instanceof Date ? lastmodValue.toISOString() : lastmodValue;
71
+ xml += ` <lastmod>${date}</lastmod>
70
72
  `;
71
- xml += ` xmlns:news="http://www.google.com/schemas/sitemap-news/0.9"
73
+ }
74
+ if (entry.changefreq) {
75
+ xml += ` <changefreq>${entry.changefreq}</changefreq>
72
76
  `;
73
- xml += ` xmlns:xhtml="http://www.w3.org/1999/xhtml">
77
+ }
78
+ if (entry.priority !== void 0) {
79
+ xml += ` <priority>${entry.priority.toFixed(1)}</priority>
74
80
  `;
75
- for (const entry of finalEntries) {
76
- const cleanMainUrl = sanitizeAndValidateUrl(entry.url, "main entry");
77
- xml += ` <url>
81
+ }
82
+ return xml;
83
+ }
84
+
85
+ // src/core/builders/image-builder.ts
86
+ function buildImageXml(images) {
87
+ if (!images?.length) return "";
88
+ let xml = "";
89
+ for (const img of images) {
90
+ const cleanImgUrl = sanitizeAndValidateUrl(img.loc, "image location");
91
+ xml += ` <image:image>
78
92
  `;
79
- xml += ` <loc>${escapeXml(cleanMainUrl)}</loc>
93
+ xml += ` <image:loc>${escapeXml(cleanImgUrl)}</image:loc>
80
94
  `;
81
- if (entry.alternates?.length) {
82
- for (const alt of entry.alternates) {
83
- const cleanAltUrl = sanitizeAndValidateUrl(alt.href, "alternate link");
84
- xml += ` <xhtml:link rel="alternate" hreflang="${escapeXml(alt.hreflang)}" href="${escapeXml(cleanAltUrl)}" />
95
+ if (img.title && img.title.trim() !== "") {
96
+ xml += ` <image:title>${escapeXml(img.title.trim())}</image:title>
85
97
  `;
86
- }
87
- }
88
- let lastmodValue = entry.lastmod;
89
- if (options.autoLastmod && !lastmodValue) {
90
- lastmodValue = now;
91
98
  }
92
- if (lastmodValue) {
93
- const date = lastmodValue instanceof Date ? lastmodValue.toISOString() : lastmodValue;
94
- xml += ` <lastmod>${date}</lastmod>
99
+ if (img.caption && img.caption.trim() !== "") {
100
+ xml += ` <image:caption>${escapeXml(img.caption.trim())}</image:caption>
95
101
  `;
96
102
  }
97
- if (entry.changefreq) {
98
- xml += ` <changefreq>${entry.changefreq}</changefreq>
103
+ if (img.geo_location && img.geo_location.trim() !== "") {
104
+ xml += ` <image:geo_location>${escapeXml(img.geo_location.trim())}</image:geo_location>
99
105
  `;
100
106
  }
101
- if (entry.priority !== void 0) {
102
- xml += ` <priority>${entry.priority.toFixed(1)}</priority>
107
+ if (img.license) {
108
+ const cleanLicenseUrl = sanitizeAndValidateUrl(img.license, "image license URL");
109
+ xml += ` <image:license>${escapeXml(cleanLicenseUrl)}</image:license>
103
110
  `;
104
111
  }
105
- if (entry.images?.length) {
106
- for (const img of entry.images) {
107
- const cleanImgUrl = sanitizeAndValidateUrl(img.loc, "image location");
108
- xml += ` <image:image>
112
+ xml += ` </image:image>
113
+ `;
114
+ }
115
+ return xml;
116
+ }
117
+
118
+ // src/core/builders/video-builder.ts
119
+ function buildVideoXml(videos) {
120
+ if (!videos?.length) return "";
121
+ let xml = "";
122
+ for (const vid of videos) {
123
+ const cleanThumbLoc = sanitizeAndValidateUrl(vid.thumbnail_loc, "video thumbnail");
124
+ const cleanContentLoc = vid.content_loc ? sanitizeAndValidateUrl(vid.content_loc, "video content location") : void 0;
125
+ const cleanPlayerLoc = vid.player_loc ? sanitizeAndValidateUrl(vid.player_loc, "video player location") : void 0;
126
+ xml += ` <video:video>
109
127
  `;
110
- xml += ` <image:loc>${escapeXml(cleanImgUrl)}</image:loc>
128
+ xml += ` <video:thumbnail_loc>${escapeXml(cleanThumbLoc)}</video:thumbnail_loc>
111
129
  `;
112
- if (img.title && img.title.trim() !== "") {
113
- xml += ` <image:title>${escapeXml(img.title.trim())}</image:title>
130
+ xml += ` <video:title>${escapeXml(vid.title)}</video:title>
114
131
  `;
115
- }
116
- if (img.caption && img.caption.trim() !== "") {
117
- xml += ` <image:caption>${escapeXml(img.caption.trim())}</image:caption>
132
+ xml += ` <video:description>${escapeXml(vid.description)}</video:description>
118
133
  `;
119
- }
120
- if (img.geo_location && img.geo_location.trim() !== "") {
121
- xml += ` <image:geo_location>${escapeXml(img.geo_location.trim())}</image:geo_location>
134
+ if (cleanContentLoc) xml += ` <video:content_loc>${escapeXml(cleanContentLoc)}</video:content_loc>
122
135
  `;
123
- }
124
- if (img.license) {
125
- const cleanLicenseUrl = sanitizeAndValidateUrl(img.license, "image license URL");
126
- xml += ` <image:license>${escapeXml(cleanLicenseUrl)}</image:license>
136
+ if (cleanPlayerLoc) xml += ` <video:player_loc>${escapeXml(cleanPlayerLoc)}</video:player_loc>
127
137
  `;
128
- }
129
- xml += ` </image:image>
138
+ if (vid.publication_date) {
139
+ const vDate = vid.publication_date instanceof Date ? vid.publication_date.toISOString() : vid.publication_date;
140
+ xml += ` <video:publication_date>${vDate}</video:publication_date>
130
141
  `;
142
+ }
143
+ if (vid.duration !== void 0) {
144
+ const finalDuration = Math.floor(vid.duration);
145
+ if (finalDuration < 0 || finalDuration > 28800) {
146
+ throw new Error(
147
+ `[next-advanced-sitemap] Invalid video duration: ${finalDuration}. Duration must be an integer between 0 and 28800 seconds (8 hours).`
148
+ );
131
149
  }
150
+ xml += ` <video:duration>${finalDuration}</video:duration>
151
+ `;
132
152
  }
133
- if (entry.videos?.length) {
134
- for (const vid of entry.videos) {
135
- const cleanThumbLoc = sanitizeAndValidateUrl(vid.thumbnail_loc, "video thumbnail");
136
- const cleanContentLoc = vid.content_loc ? sanitizeAndValidateUrl(vid.content_loc, "video content location") : void 0;
137
- const cleanPlayerLoc = vid.player_loc ? sanitizeAndValidateUrl(vid.player_loc, "video player location") : void 0;
138
- xml += ` <video:video>
153
+ if (vid.view_count !== void 0) {
154
+ const finalViewCount = Math.floor(vid.view_count);
155
+ if (finalViewCount < 0) {
156
+ throw new Error(
157
+ `[next-advanced-sitemap] Invalid video view_count: ${finalViewCount}. View count cannot be negative.`
158
+ );
159
+ }
160
+ xml += ` <video:view_count>${finalViewCount}</video:view_count>
139
161
  `;
140
- xml += ` <video:thumbnail_loc>${escapeXml(cleanThumbLoc)}</video:thumbnail_loc>
162
+ }
163
+ if (vid.live) {
164
+ xml += ` <video:live>${vid.live}</video:live>
141
165
  `;
142
- xml += ` <video:title>${escapeXml(vid.title)}</video:title>
166
+ }
167
+ if (vid.restriction) {
168
+ if (!vid.restriction.countries || vid.restriction.countries.length === 0) {
169
+ throw new Error(
170
+ `[next-advanced-sitemap] Invalid video restriction: countries array cannot be empty.`
171
+ );
172
+ }
173
+ const cleanCountries = vid.restriction.countries.map((country) => {
174
+ const code = country.trim().toUpperCase();
175
+ if (code.length < 2 || code.length > 3) {
176
+ throw new Error(
177
+ `[next-advanced-sitemap] Invalid ISO country code detected: "${country}". Must be a valid ISO 3166 code.`
178
+ );
179
+ }
180
+ return code;
181
+ });
182
+ const countriesStr = cleanCountries.join(" ");
183
+ xml += ` <video:restriction relationship="${vid.restriction.relationship}">${countriesStr}</video:restriction>
143
184
  `;
144
- xml += ` <video:description>${escapeXml(vid.description)}</video:description>
185
+ }
186
+ if (vid.platform) {
187
+ if (!vid.platform.platforms || vid.platform.platforms.length === 0) {
188
+ throw new Error(
189
+ `[next-advanced-sitemap] Invalid video platform: platforms array cannot be empty.`
190
+ );
191
+ }
192
+ const validPlatforms = ["web", "mobile", "tv"];
193
+ for (const p of vid.platform.platforms) {
194
+ if (!validPlatforms.includes(p)) {
195
+ throw new Error(
196
+ `[next-advanced-sitemap] Invalid platform type: "${p}". Allowed values are 'web', 'mobile', or 'tv'.`
197
+ );
198
+ }
199
+ }
200
+ const platformsStr = vid.platform.platforms.join(" ");
201
+ xml += ` <video:platform relationship="${vid.platform.relationship}">${platformsStr}</video:platform>
145
202
  `;
146
- if (cleanContentLoc) xml += ` <video:content_loc>${escapeXml(cleanContentLoc)}</video:content_loc>
203
+ }
204
+ if (vid.requires_subscription !== void 0) {
205
+ let subValue;
206
+ if (typeof vid.requires_subscription === "boolean") {
207
+ subValue = vid.requires_subscription ? "yes" : "no";
208
+ } else if (vid.requires_subscription === "yes" || vid.requires_subscription === "no") {
209
+ subValue = vid.requires_subscription;
210
+ } else {
211
+ throw new Error(
212
+ `[next-advanced-sitemap] Invalid value for requires_subscription: "${vid.requires_subscription}". Expected boolean or strict string 'yes' | 'no'.`
213
+ );
214
+ }
215
+ xml += ` <video:requires_subscription>${subValue}</video:requires_subscription>
147
216
  `;
148
- if (cleanPlayerLoc) xml += ` <video:player_loc>${escapeXml(cleanPlayerLoc)}</video:player_loc>
217
+ }
218
+ xml += ` </video:video>
149
219
  `;
150
- if (vid.publication_date) {
151
- const vDate = vid.publication_date instanceof Date ? vid.publication_date.toISOString() : vid.publication_date;
152
- xml += ` <video:publication_date>${vDate}</video:publication_date>
220
+ }
221
+ return xml;
222
+ }
223
+
224
+ // src/core/builders/news-builder.ts
225
+ function buildNewsXml(news) {
226
+ if (!news) return "";
227
+ const nDate = news.publication_date instanceof Date ? news.publication_date.toISOString() : news.publication_date;
228
+ let xml = "";
229
+ xml += ` <news:news>
153
230
  `;
154
- }
155
- if (vid.duration !== void 0) {
156
- const finalDuration = Math.floor(vid.duration);
157
- if (finalDuration < 0 || finalDuration > 28800) {
158
- throw new Error(
159
- `[next-advanced-sitemap] Invalid video duration: ${finalDuration}. Duration must be an integer between 0 and 28800 seconds (8 hours).`
160
- );
161
- }
162
- xml += ` <video:duration>${finalDuration}</video:duration>
231
+ xml += ` <news:publication>
163
232
  `;
164
- }
165
- if (vid.view_count !== void 0) {
166
- const finalViewCount = Math.floor(vid.view_count);
167
- if (finalViewCount < 0) {
168
- throw new Error(
169
- `[next-advanced-sitemap] Invalid video view_count: ${finalViewCount}. View count cannot be negative.`
170
- );
171
- }
172
- xml += ` <video:view_count>${finalViewCount}</video:view_count>
233
+ xml += ` <news:name>${escapeXml(news.name)}</news:name>
173
234
  `;
174
- }
175
- if (vid.live) {
176
- xml += ` <video:live>${vid.live}</video:live>
235
+ xml += ` <news:language>${escapeXml(news.language)}</news:language>
177
236
  `;
178
- }
179
- if (vid.restriction) {
180
- if (!vid.restriction.countries || vid.restriction.countries.length === 0) {
181
- throw new Error(
182
- `[next-advanced-sitemap] Invalid video restriction: countries array cannot be empty.`
183
- );
184
- }
185
- const cleanCountries = vid.restriction.countries.map((country) => {
186
- const code = country.trim().toUpperCase();
187
- if (code.length < 2 || code.length > 3) {
188
- throw new Error(
189
- `[next-advanced-sitemap] Invalid ISO country code detected: "${country}". Must be a valid ISO 3166 code.`
190
- );
191
- }
192
- return code;
193
- });
194
- const countriesStr = cleanCountries.join(" ");
195
- xml += ` <video:restriction relationship="${vid.restriction.relationship}">${countriesStr}</video:restriction>
237
+ xml += ` </news:publication>
196
238
  `;
197
- }
198
- if (vid.platform) {
199
- if (!vid.platform.platforms || vid.platform.platforms.length === 0) {
200
- throw new Error(
201
- `[next-advanced-sitemap] Invalid video platform: platforms array cannot be empty.`
202
- );
203
- }
204
- const validPlatforms = ["web", "mobile", "tv"];
205
- for (const p of vid.platform.platforms) {
206
- if (!validPlatforms.includes(p)) {
207
- throw new Error(
208
- `[next-advanced-sitemap] Invalid platform type: "${p}". Allowed values are 'web', 'mobile', or 'tv'.`
209
- );
210
- }
211
- }
212
- const platformsStr = vid.platform.platforms.join(" ");
213
- xml += ` <video:platform relationship="${vid.platform.relationship}">${platformsStr}</video:platform>
239
+ xml += ` <news:publication_date>${nDate}</news:publication_date>
214
240
  `;
215
- }
216
- xml += ` </video:video>
241
+ xml += ` <news:title>${escapeXml(news.title)}</news:title>
217
242
  `;
218
- }
219
- }
220
- if (entry.news) {
221
- const nDate = entry.news.publication_date instanceof Date ? entry.news.publication_date.toISOString() : entry.news.publication_date;
222
- xml += ` <news:news>
243
+ xml += ` </news:news>
223
244
  `;
224
- xml += ` <news:publication>
245
+ return xml;
246
+ }
247
+
248
+ // src/core/generator.ts
249
+ function generateXml(entries, options = {}) {
250
+ const now = (/* @__PURE__ */ new Date()).toISOString();
251
+ let finalEntries = [...entries];
252
+ if (options.sortByPriority) {
253
+ finalEntries.sort((a, b) => {
254
+ const priorityA = a.priority !== void 0 ? a.priority : 0.5;
255
+ const priorityB = b.priority !== void 0 ? b.priority : 0.5;
256
+ return priorityB - priorityA;
257
+ });
258
+ }
259
+ let xml = `<?xml version="1.0" encoding="UTF-8"?>
225
260
  `;
226
- xml += ` <news:name>${escapeXml(entry.news.name)}</news:name>
261
+ xml += `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
227
262
  `;
228
- xml += ` <news:language>${escapeXml(entry.news.language)}</news:language>
263
+ xml += ` xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"
229
264
  `;
230
- xml += ` </news:publication>
265
+ xml += ` xmlns:video="http://www.google.com/schemas/sitemap-video/1.1"
231
266
  `;
232
- xml += ` <news:publication_date>${nDate}</news:publication_date>
267
+ xml += ` xmlns:news="http://www.google.com/schemas/sitemap-news/0.9"
233
268
  `;
234
- xml += ` <news:title>${escapeXml(entry.news.title)}</news:title>
269
+ xml += ` xmlns:xhtml="http://www.w3.org/1999/xhtml">
235
270
  `;
236
- xml += ` </news:news>
271
+ for (const entry of finalEntries) {
272
+ xml += ` <url>
237
273
  `;
238
- }
274
+ xml += buildUrlBaseXml(entry, options, now);
275
+ xml += buildImageXml(entry.images);
276
+ xml += buildVideoXml(entry.videos);
277
+ xml += buildNewsXml(entry.news);
239
278
  xml += ` </url>
240
279
  `;
241
280
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils/xml-escape.ts","../src/core/generator.ts","../src/index.ts"],"sourcesContent":["/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\n/**\n * Convertit les caractères spéciaux en entités XML pour éviter la corruption du fichier.\n * Gère : <, >, &, \", '\n */\nexport function escapeXml(unsafe: string | undefined | null): string {\n if (!unsafe) return '';\n \n return unsafe.replace(/[<>&\"']/g, (c) => {\n switch (c) {\n case '<': return '&lt;';\n case '>': return '&gt;';\n case '&': return '&amp;';\n case '\"': return '&quot;';\n case \"'\": return '&apos;';\n default: return c;\n }\n });\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry, SitemapOptions } from '../types/sitemap.js';\nimport { escapeXml } from '../utils/xml-escape.js';\n\n/**\n * Nettoie et valide de manière stricte le format et la structure d'une URL.\n * v1.0.7 : Intégration de l'Auto-Trimming (nettoyage des espaces de début et de fin)\n */\nfunction 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 flux XML complet du sitemap incluant les extensions Images, Vidéos, News et Hreflang.\n * v1.1.4 : Prise en charge et validation stricte des restrictions pays (ISO 3166) et plateformes (web, mobile, tv)\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 const cleanMainUrl = sanitizeAndValidateUrl(entry.url, 'main entry');\n\n xml += ` <url>\\n`;\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 = now;\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 // Extension Images\n if (entry.images?.length) {\n for (const img of entry.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\n // Extension Vidéos\n if (entry.videos?.length) {\n for (const vid of entry.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 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 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 xml += ` </video:video>\\n`;\n }\n }\n\n // Extension News\n if (entry.news) {\n const nDate = entry.news.publication_date instanceof Date ? entry.news.publication_date.toISOString() : entry.news.publication_date;\n xml += ` <news:news>\\n`;\n xml += ` <news:publication>\\n`;\n xml += ` <news:name>${escapeXml(entry.news.name)}</news:name>\\n`;\n xml += ` <news:language>${escapeXml(entry.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(entry.news.title)}</news:title>\\n`;\n xml += ` </news:news>\\n`;\n }\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":";AASO,SAAS,UAAU,QAA2C;AACnE,MAAI,CAAC,OAAQ,QAAO;AAEpB,SAAO,OAAO,QAAQ,YAAY,CAAC,MAAM;AACvC,YAAQ,GAAG;AAAA,MACT,KAAK;AAAK,eAAO;AAAA,MACjB,KAAK;AAAK,eAAO;AAAA,MACjB,KAAK;AAAK,eAAO;AAAA,MACjB,KAAK;AAAK,eAAO;AAAA,MACjB,KAAK;AAAK,eAAO;AAAA,MACjB;AAAS,eAAO;AAAA,IAClB;AAAA,EACF,CAAC;AACH;;;ACVA,SAAS,uBAAuB,QAAgB,SAAyB;AACvE,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;AAMO,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;AAChC,UAAM,eAAe,uBAAuB,MAAM,KAAK,YAAY;AAEnE,WAAO;AAAA;AACP,WAAO,YAAY,UAAU,YAAY,CAAC;AAAA;AAE1C,QAAI,MAAM,YAAY,QAAQ;AAC5B,iBAAW,OAAO,MAAM,YAAY;AAClC,cAAM,cAAc,uBAAuB,IAAI,MAAM,gBAAgB;AACrE,eAAO,6CAA6C,UAAU,IAAI,QAAQ,CAAC,WAAW,UAAU,WAAW,CAAC;AAAA;AAAA,MAC9G;AAAA,IACF;AAEA,QAAI,eAAe,MAAM;AACzB,QAAI,QAAQ,eAAe,CAAC,cAAc;AACxC,qBAAe;AAAA,IACjB;AAEA,QAAI,cAAc;AAChB,YAAM,OAAO,wBAAwB,OAAO,aAAa,YAAY,IAAI;AACzE,aAAO,gBAAgB,IAAI;AAAA;AAAA,IAC7B;AAEA,QAAI,MAAM,YAAY;AACpB,aAAO,mBAAmB,MAAM,UAAU;AAAA;AAAA,IAC5C;AAEA,QAAI,MAAM,aAAa,QAAW;AAChC,aAAO,iBAAkB,MAAM,SAAoB,QAAQ,CAAC,CAAC;AAAA;AAAA,IAC/D;AAGA,QAAI,MAAM,QAAQ,QAAQ;AACxB,iBAAW,OAAO,MAAM,QAAQ;AAC9B,cAAM,cAAc,uBAAuB,IAAI,KAAK,gBAAgB;AAEpE,eAAO;AAAA;AACP,eAAO,oBAAoB,UAAU,WAAW,CAAC;AAAA;AAEjD,YAAI,IAAI,SAAS,IAAI,MAAM,KAAK,MAAM,IAAI;AACxC,iBAAO,sBAAsB,UAAU,IAAI,MAAM,KAAK,CAAC,CAAC;AAAA;AAAA,QAC1D;AACA,YAAI,IAAI,WAAW,IAAI,QAAQ,KAAK,MAAM,IAAI;AAC5C,iBAAO,wBAAwB,UAAU,IAAI,QAAQ,KAAK,CAAC,CAAC;AAAA;AAAA,QAC9D;AAEA,YAAI,IAAI,gBAAgB,IAAI,aAAa,KAAK,MAAM,IAAI;AACtD,iBAAO,6BAA6B,UAAU,IAAI,aAAa,KAAK,CAAC,CAAC;AAAA;AAAA,QACxE;AAEA,YAAI,IAAI,SAAS;AACf,gBAAM,kBAAkB,uBAAuB,IAAI,SAAS,mBAAmB;AAC/E,iBAAO,wBAAwB,UAAU,eAAe,CAAC;AAAA;AAAA,QAC3D;AAEA,eAAO;AAAA;AAAA,MACT;AAAA,IACF;AAGA,QAAI,MAAM,QAAQ,QAAQ;AACxB,iBAAW,OAAO,MAAM,QAAQ;AAC9B,cAAM,gBAAgB,uBAAuB,IAAI,eAAe,iBAAiB;AACjF,cAAM,kBAAkB,IAAI,cAAc,uBAAuB,IAAI,aAAa,wBAAwB,IAAI;AAC9G,cAAM,iBAAiB,IAAI,aAAa,uBAAuB,IAAI,YAAY,uBAAuB,IAAI;AAE1G,eAAO;AAAA;AACP,eAAO,8BAA8B,UAAU,aAAa,CAAC;AAAA;AAC7D,eAAO,sBAAsB,UAAU,IAAI,KAAK,CAAC;AAAA;AACjD,eAAO,4BAA4B,UAAU,IAAI,WAAW,CAAC;AAAA;AAE7D,YAAI,gBAAiB,QAAO,4BAA4B,UAAU,eAAe,CAAC;AAAA;AAClF,YAAI,eAAgB,QAAO,2BAA2B,UAAU,cAAc,CAAC;AAAA;AAE/E,YAAI,IAAI,kBAAkB;AACxB,gBAAM,QAAQ,IAAI,4BAA4B,OAAO,IAAI,iBAAiB,YAAY,IAAI,IAAI;AAC9F,iBAAO,iCAAiC,KAAK;AAAA;AAAA,QAC/C;AAEA,YAAI,IAAI,aAAa,QAAW;AAC9B,gBAAM,gBAAgB,KAAK,MAAM,IAAI,QAAQ;AAC7C,cAAI,gBAAgB,KAAK,gBAAgB,OAAO;AAC9C,kBAAM,IAAI;AAAA,cACR,mDAAmD,aAAa;AAAA,YAClE;AAAA,UACF;AACA,iBAAO,yBAAyB,aAAa;AAAA;AAAA,QAC/C;AAEA,YAAI,IAAI,eAAe,QAAW;AAChC,gBAAM,iBAAiB,KAAK,MAAM,IAAI,UAAU;AAChD,cAAI,iBAAiB,GAAG;AACtB,kBAAM,IAAI;AAAA,cACR,qDAAqD,cAAc;AAAA,YACrE;AAAA,UACF;AACA,iBAAO,2BAA2B,cAAc;AAAA;AAAA,QAClD;AAEA,YAAI,IAAI,MAAM;AACZ,iBAAO,qBAAqB,IAAI,IAAI;AAAA;AAAA,QACtC;AAGA,YAAI,IAAI,aAAa;AACnB,cAAI,CAAC,IAAI,YAAY,aAAa,IAAI,YAAY,UAAU,WAAW,GAAG;AACxE,kBAAM,IAAI;AAAA,cACR;AAAA,YACF;AAAA,UACF;AAEA,gBAAM,iBAAiB,IAAI,YAAY,UAAU,IAAI,aAAW;AAC9D,kBAAM,OAAO,QAAQ,KAAK,EAAE,YAAY;AACxC,gBAAI,KAAK,SAAS,KAAK,KAAK,SAAS,GAAG;AACtC,oBAAM,IAAI;AAAA,gBACR,+DAA+D,OAAO;AAAA,cACxE;AAAA,YACF;AACA,mBAAO;AAAA,UACT,CAAC;AAED,gBAAM,eAAe,eAAe,KAAK,GAAG;AAC5C,iBAAO,0CAA0C,IAAI,YAAY,YAAY,KAAK,YAAY;AAAA;AAAA,QAChG;AAGA,YAAI,IAAI,UAAU;AAChB,cAAI,CAAC,IAAI,SAAS,aAAa,IAAI,SAAS,UAAU,WAAW,GAAG;AAClE,kBAAM,IAAI;AAAA,cACR;AAAA,YACF;AAAA,UACF;AAEA,gBAAM,iBAAiB,CAAC,OAAO,UAAU,IAAI;AAC7C,qBAAW,KAAK,IAAI,SAAS,WAAW;AACtC,gBAAI,CAAC,eAAe,SAAS,CAAC,GAAG;AAC/B,oBAAM,IAAI;AAAA,gBACR,mDAAmD,CAAC;AAAA,cACtD;AAAA,YACF;AAAA,UACF;AAEA,gBAAM,eAAe,IAAI,SAAS,UAAU,KAAK,GAAG;AACpD,iBAAO,uCAAuC,IAAI,SAAS,YAAY,KAAK,YAAY;AAAA;AAAA,QAC1F;AAEA,eAAO;AAAA;AAAA,MACT;AAAA,IACF;AAGA,QAAI,MAAM,MAAM;AACd,YAAM,QAAQ,MAAM,KAAK,4BAA4B,OAAO,MAAM,KAAK,iBAAiB,YAAY,IAAI,MAAM,KAAK;AACnH,aAAO;AAAA;AACP,aAAO;AAAA;AACP,aAAO,sBAAsB,UAAU,MAAM,KAAK,IAAI,CAAC;AAAA;AACvD,aAAO,0BAA0B,UAAU,MAAM,KAAK,QAAQ,CAAC;AAAA;AAC/D,aAAO;AAAA;AACP,aAAO,gCAAgC,KAAK;AAAA;AAC5C,aAAO,qBAAqB,UAAU,MAAM,KAAK,KAAK,CAAC;AAAA;AACvD,aAAO;AAAA;AAAA,IACT;AAEA,WAAO;AAAA;AAAA,EACT;AAEA,SAAO;AACP,SAAO;AACT;;;AC9NO,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;","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/generator.ts","../src/index.ts"],"sourcesContent":["/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\n/**\n * Convertit les caractères spéciaux en entités XML pour éviter la corruption du fichier.\n * Gère : <, >, &, \", '\n */\nexport function escapeXml(unsafe: string | undefined | null): string {\n if (!unsafe) return '';\n \n return unsafe.replace(/[<>&\"']/g, (c) => {\n switch (c) {\n case '<': return '&lt;';\n case '>': return '&gt;';\n case '&': return '&amp;';\n case '\"': return '&quot;';\n case \"'\": return '&apos;';\n default: return c;\n }\n });\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry, SitemapOptions } from '../../types/sitemap.js';\nimport { escapeXml } from '../../utils/xml-escape.js';\n\n/**\n * Nettoie et valide de manière stricte le format et la structure d'une URL.\n * v1.0.7 : Intégration de l'Auto-Trimming (nettoyage des espaces de début et de fin)\n */\nexport function sanitizeAndValidateUrl(rawUrl: string, context: string): string {\n const url = rawUrl ? rawUrl.trim() : '';\n\n if (!url.startsWith('http://') && !url.startsWith('https://')) {\n throw new Error(\n `[next-advanced-sitemap] Invalid URL in ${context}: \"${url}\". URLs must start with http:// or https://`\n );\n }\n\n if (url.includes(' ')) {\n throw new Error(\n `[next-advanced-sitemap] Malformed URL structure detected in ${context}: \"${url}\". Please verify spaces or special characters.`\n );\n }\n\n let isValid = false;\n if (typeof URL.canParse === 'function') {\n isValid = URL.canParse(url);\n } else {\n try {\n new URL(url);\n isValid = true;\n } catch {\n isValid = false;\n }\n }\n\n if (!isValid) {\n throw new Error(\n `[next-advanced-sitemap] Malformed URL structure detected in ${context}: \"${url}\". Please verify spaces or special characters.`\n );\n }\n\n return url;\n}\n\n/**\n * Génère le bloc XML de base pour un nœud URL (loc, alternates, lastmod, changefreq, priority).\n */\nexport function buildUrlBaseXml(entry: SitemapEntry, options: SitemapOptions, nowIso: string): string {\n let xml = '';\n \n const cleanMainUrl = sanitizeAndValidateUrl(entry.url, 'main entry');\n xml += ` <loc>${escapeXml(cleanMainUrl)}</loc>\\n`;\n\n if (entry.alternates?.length) {\n for (const alt of entry.alternates) {\n const cleanAltUrl = sanitizeAndValidateUrl(alt.href, 'alternate link');\n xml += ` <xhtml:link rel=\"alternate\" hreflang=\"${escapeXml(alt.hreflang)}\" href=\"${escapeXml(cleanAltUrl)}\" />\\n`;\n }\n }\n\n let lastmodValue = entry.lastmod;\n if (options.autoLastmod && !lastmodValue) {\n lastmodValue = nowIso;\n }\n\n if (lastmodValue) {\n const date = lastmodValue instanceof Date ? lastmodValue.toISOString() : lastmodValue;\n xml += ` <lastmod>${date}</lastmod>\\n`;\n }\n\n if (entry.changefreq) {\n xml += ` <changefreq>${entry.changefreq}</changefreq>\\n`;\n }\n\n if (entry.priority !== undefined) {\n xml += ` <priority>${(entry.priority as number).toFixed(1)}</priority>\\n`;\n }\n\n return xml;\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry } from '../../types/sitemap.js';\nimport { escapeXml } from '../../utils/xml-escape.js';\nimport { sanitizeAndValidateUrl } from './url-builder.js';\n\nexport function buildImageXml(images: SitemapEntry['images']): string {\n if (!images?.length) return '';\n\n let xml = '';\n for (const img of images) {\n const cleanImgUrl = sanitizeAndValidateUrl(img.loc, 'image location');\n \n xml += ` <image:image>\\n`;\n xml += ` <image:loc>${escapeXml(cleanImgUrl)}</image:loc>\\n`;\n \n if (img.title && img.title.trim() !== '') {\n xml += ` <image:title>${escapeXml(img.title.trim())}</image:title>\\n`;\n }\n if (img.caption && img.caption.trim() !== '') {\n xml += ` <image:caption>${escapeXml(img.caption.trim())}</image:caption>\\n`;\n }\n \n if (img.geo_location && img.geo_location.trim() !== '') {\n xml += ` <image:geo_location>${escapeXml(img.geo_location.trim())}</image:geo_location>\\n`;\n }\n \n if (img.license) {\n const cleanLicenseUrl = sanitizeAndValidateUrl(img.license, 'image license URL');\n xml += ` <image:license>${escapeXml(cleanLicenseUrl)}</image:license>\\n`;\n }\n \n xml += ` </image:image>\\n`;\n }\n \n return xml;\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \n * Licensed under FomaDev Public License.\n * See LICENSE file in the project root for full license information.\n */\n\nimport { SitemapEntry } from '../../types/sitemap.js';\nimport { escapeXml } from '../../utils/xml-escape.js';\nimport { sanitizeAndValidateUrl } from './url-builder.js';\n\nexport function buildVideoXml(videos: SitemapEntry['videos']): string {\n if (!videos?.length) return '';\n\n let xml = '';\n for (const vid of videos) {\n const cleanThumbLoc = sanitizeAndValidateUrl(vid.thumbnail_loc, 'video thumbnail');\n const cleanContentLoc = vid.content_loc ? sanitizeAndValidateUrl(vid.content_loc, 'video content location') : undefined;\n const cleanPlayerLoc = vid.player_loc ? sanitizeAndValidateUrl(vid.player_loc, 'video player location') : undefined;\n\n xml += ` <video:video>\\n`;\n xml += ` <video:thumbnail_loc>${escapeXml(cleanThumbLoc)}</video:thumbnail_loc>\\n`;\n xml += ` <video:title>${escapeXml(vid.title)}</video:title>\\n`;\n xml += ` <video:description>${escapeXml(vid.description)}</video:description>\\n`;\n \n if (cleanContentLoc) xml += ` <video:content_loc>${escapeXml(cleanContentLoc)}</video:content_loc>\\n`;\n if (cleanPlayerLoc) xml += ` <video:player_loc>${escapeXml(cleanPlayerLoc)}</video:player_loc>\\n`;\n \n if (vid.publication_date) {\n const vDate = vid.publication_date instanceof Date ? vid.publication_date.toISOString() : vid.publication_date;\n xml += ` <video:publication_date>${vDate}</video:publication_date>\\n`;\n }\n\n // ✨ Validation et Sérialisation de la durée (0 - 28800s)\n if (vid.duration !== undefined) {\n const finalDuration = Math.floor(vid.duration);\n if (finalDuration < 0 || finalDuration > 28800) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video duration: ${finalDuration}. Duration must be an integer between 0 and 28800 seconds (8 hours).`\n );\n }\n xml += ` <video:duration>${finalDuration}</video:duration>\\n`;\n }\n\n // ✨ Validation et Sérialisation du nombre de vues (>= 0)\n if (vid.view_count !== undefined) {\n const finalViewCount = Math.floor(vid.view_count);\n if (finalViewCount < 0) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video view_count: ${finalViewCount}. View count cannot be negative.`\n );\n }\n xml += ` <video:view_count>${finalViewCount}</video:view_count>\\n`;\n }\n\n if (vid.live) {\n xml += ` <video:live>${vid.live}</video:live>\\n`;\n }\n\n // ✨ Validation et Sérialisation des Restrictions Pays (v1.1.4)\n if (vid.restriction) {\n if (!vid.restriction.countries || vid.restriction.countries.length === 0) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video restriction: countries array cannot be empty.`\n );\n }\n\n const cleanCountries = vid.restriction.countries.map(country => {\n const code = country.trim().toUpperCase();\n if (code.length < 2 || code.length > 3) {\n throw new Error(\n `[next-advanced-sitemap] Invalid ISO country code detected: \"${country}\". Must be a valid ISO 3166 code.`\n );\n }\n return code;\n });\n\n const countriesStr = cleanCountries.join(' ');\n xml += ` <video:restriction relationship=\"${vid.restriction.relationship}\">${countriesStr}</video:restriction>\\n`;\n }\n\n // ✨ Validation et Sérialisation des Plateformes (v1.1.4)\n if (vid.platform) {\n if (!vid.platform.platforms || vid.platform.platforms.length === 0) {\n throw new Error(\n `[next-advanced-sitemap] Invalid video platform: platforms array cannot be empty.`\n );\n }\n\n const validPlatforms = ['web', 'mobile', 'tv'];\n for (const p of vid.platform.platforms) {\n if (!validPlatforms.includes(p)) {\n throw new Error(\n `[next-advanced-sitemap] Invalid platform type: \"${p}\". Allowed values are 'web', 'mobile', or 'tv'.`\n );\n }\n }\n\n const platformsStr = vid.platform.platforms.join(' ');\n xml += ` <video:platform relationship=\"${vid.platform.relationship}\">${platformsStr}</video:platform>\\n`;\n }\n \n if (vid.requires_subscription !== undefined) {\n let subValue: 'yes' | 'no';\n\n if (typeof vid.requires_subscription === 'boolean') {\n subValue = vid.requires_subscription ? 'yes' : 'no';\n } else if (vid.requires_subscription === 'yes' || vid.requires_subscription === 'no') {\n subValue = vid.requires_subscription;\n } else {\n throw new Error(\n `[next-advanced-sitemap] Invalid value for requires_subscription: \"${vid.requires_subscription}\". Expected boolean or strict string 'yes' | 'no'.`\n );\n }\n\n xml += ` <video:requires_subscription>${subValue}</video:requires_subscription>\\n`;\n }\n\n 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 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, 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';\n\n/**\n * Génère le flux XML complet du sitemap incluant les extensions Images, Vidéos, News et Hreflang.\n * v1.1.4 : Version découplée et hautement modulaire.\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 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":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-advanced-sitemap",
3
- "version": "1.1.4",
3
+ "version": "1.1.5",
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, zero-dependency, and built for App Router.",
6
6
  "main": "./dist/index.cjs",