@power-seo/sitemap 1.0.11 → 1.0.15
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 +28 -29
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +9 -3
package/README.md
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
# @power-seo/sitemap
|
|
2
2
|
|
|
3
|
-

|
|
4
4
|
|
|
5
5
|
XML sitemap generation for TypeScript — streaming output, automatic index splitting, image/video/news extensions, and URL validation — works in Next.js, Remix, Express, and edge runtimes with zero runtime dependencies.
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/@power-seo/sitemap)
|
|
8
8
|
[](https://www.npmjs.com/package/@power-seo/sitemap)
|
|
9
9
|
[](https://socket.dev/npm/package/@power-seo/sitemap)
|
|
10
|
-
[](https://github.com/CyberCraftBD/power-seo/actions)
|
|
11
10
|
[](https://opensource.org/licenses/MIT)
|
|
12
11
|
[](https://www.typescriptlang.org/)
|
|
13
12
|
[](https://bundlephobia.com/package/@power-seo/sitemap)
|
|
14
13
|
|
|
15
|
-
`@power-seo/sitemap` produces standards-compliant `<urlset>` and `<sitemapindex>` XML from typed URL arrays. Provide a hostname and URL list — get back a valid XML string ready to serve as `Content-Type: application/xml`. For large catalogs, stream chunks with constant memory usage or auto-split at the 50,000-URL spec limit with a generated index file. All
|
|
14
|
+
`@power-seo/sitemap` produces standards-compliant `<urlset>` and `<sitemapindex>` XML from typed URL arrays. Provide a hostname and URL list — get back a valid XML string ready to serve as `Content-Type: application/xml`. For large catalogs, stream chunks with constant memory usage or auto-split at the 50,000-URL spec limit with a generated index file. All six functions are independently importable and tree-shakeable.
|
|
16
15
|
|
|
17
16
|
> **Zero runtime dependencies** — only `@power-seo/core` as a peer.
|
|
18
17
|
|
|
@@ -31,7 +30,13 @@ XML sitemap generation for TypeScript — streaming output, automatic index spli
|
|
|
31
30
|
| Hostname handling | ❌ Hardcode absolute URLs everywhere | ✅ Pass `hostname` once; use relative `loc` paths |
|
|
32
31
|
| Validation | ❌ Silent bad data reaches Google | ✅ `validateSitemapUrl()` returns errors + warnings |
|
|
33
32
|
|
|
34
|
-

|
|
34
|
+
|
|
35
|
+
<p align="left">
|
|
36
|
+
<a href="https://www.buymeacoffee.com/ccbd.dev" target="_blank">
|
|
37
|
+
<img src="https://img.buymeacoffee.com/button-api/?text=Buy%20me%20a%20coffee&emoji=&slug=ccbd.dev&button_colour=FFDD00&font_colour=000000&font_family=Cookie&outline_colour=000000&coffee_colour=ffffff" />
|
|
38
|
+
</a>
|
|
39
|
+
</p>
|
|
35
40
|
|
|
36
41
|
---
|
|
37
42
|
|
|
@@ -45,15 +50,16 @@ XML sitemap generation for TypeScript — streaming output, automatic index spli
|
|
|
45
50
|
- **Streaming generation** — `streamSitemap()` is a synchronous generator yielding XML string chunks; no memory spike on large lists
|
|
46
51
|
- **Automatic index splitting** — `splitSitemap()` chunks at `MAX_URLS_PER_SITEMAP` (50,000) and returns both sitemaps and the index XML
|
|
47
52
|
- **Sitemap index generation** — `generateSitemapIndex()` creates a `<sitemapindex>` pointing to child sitemaps
|
|
53
|
+
- **Smart namespace detection** — `generateSitemap()` only declares XML namespaces for extensions (image, video, news) that are actually used
|
|
48
54
|
- **URL validation** — `validateSitemapUrl()` returns `{ valid, errors, warnings }` without throwing
|
|
49
|
-
- **Next.js App Router adapter** — `toNextSitemap()`
|
|
55
|
+
- **Next.js App Router adapter** — `toNextSitemap()` converts `SitemapURL[]` to the `MetadataRoute.Sitemap[]` format for `app/sitemap.ts`
|
|
50
56
|
- **Constants exported** — `MAX_URLS_PER_SITEMAP` (50,000) and `MAX_SITEMAP_SIZE_BYTES` (52,428,800)
|
|
51
57
|
- **Framework-agnostic** — works in Next.js API routes, Remix loaders, Express, Fastify, and edge runtimes
|
|
52
58
|
- **Full TypeScript support** — typed `SitemapURL`, `SitemapImage`, `SitemapVideo`, `SitemapNews`, `SitemapConfig`
|
|
53
59
|
- **Zero runtime dependencies** — pure TypeScript, no external XML libraries
|
|
54
60
|
- **Tree-shakeable** — import only the functions you use
|
|
55
61
|
|
|
56
|
-

|
|
57
63
|
|
|
58
64
|
---
|
|
59
65
|
|
|
@@ -74,7 +80,7 @@ XML sitemap generation for TypeScript — streaming output, automatic index spli
|
|
|
74
80
|
| Tree-shakeable | ✅ | ❌ | ❌ | ❌ |
|
|
75
81
|
| Next.js `app/sitemap.ts` adapter | ✅ | ✅ | ❌ | ❌ |
|
|
76
82
|
|
|
77
|
-

|
|
78
84
|
|
|
79
85
|
---
|
|
80
86
|
|
|
@@ -116,7 +122,7 @@ const xml = generateSitemap({
|
|
|
116
122
|
|
|
117
123
|
`hostname` is required — it is prepended to any `loc` value that is a relative path. Absolute `loc` values (starting with `http`) are used as-is.
|
|
118
124
|
|
|
119
|
-

|
|
120
126
|
|
|
121
127
|
---
|
|
122
128
|
|
|
@@ -254,11 +260,11 @@ const result = validateSitemapUrl({
|
|
|
254
260
|
|
|
255
261
|
### Next.js App Router — `app/sitemap.ts` Convention
|
|
256
262
|
|
|
257
|
-
Next.js App Router has a built-in `app/sitemap.ts` file convention that returns an array of URL objects (not XML). Use `toNextSitemap()`
|
|
263
|
+
Next.js App Router has a built-in `app/sitemap.ts` file convention that returns an array of URL objects (not XML). Use `toNextSitemap()` to convert `SitemapURL[]` to the required format:
|
|
258
264
|
|
|
259
265
|
```ts
|
|
260
266
|
// app/sitemap.ts
|
|
261
|
-
import { toNextSitemap } from '@power-seo/sitemap
|
|
267
|
+
import { toNextSitemap } from '@power-seo/sitemap';
|
|
262
268
|
|
|
263
269
|
export default async function sitemap() {
|
|
264
270
|
const urls = await fetchUrlsFromCms();
|
|
@@ -268,7 +274,7 @@ export default async function sitemap() {
|
|
|
268
274
|
}
|
|
269
275
|
```
|
|
270
276
|
|
|
271
|
-
`toNextSitemap()` filters out invalid URLs
|
|
277
|
+
`toNextSitemap()` filters out invalid URLs and maps `changefreq` to `changeFrequency` as required by Next.js. The `lastmod` field is passed through as-is (string or Date).
|
|
272
278
|
|
|
273
279
|
### Next.js App Router — Route Handler (XML)
|
|
274
280
|
|
|
@@ -387,22 +393,22 @@ function validateSitemapUrl(url: SitemapURL): SitemapValidationResult;
|
|
|
387
393
|
|
|
388
394
|
Returns `{ valid: boolean; errors: string[]; warnings: string[] }`. Never throws.
|
|
389
395
|
|
|
390
|
-
### `toNextSitemap(urls)`
|
|
396
|
+
### `toNextSitemap(urls)`
|
|
391
397
|
|
|
392
398
|
```ts
|
|
393
|
-
import { toNextSitemap } from '@power-seo/sitemap
|
|
399
|
+
import { toNextSitemap } from '@power-seo/sitemap';
|
|
394
400
|
|
|
395
401
|
function toNextSitemap(urls: SitemapURL[]): NextSitemapEntry[];
|
|
396
402
|
```
|
|
397
403
|
|
|
398
|
-
Converts a `SitemapURL[]` to the array format expected by Next.js App Router's `app/sitemap.ts` file convention. Invalid URLs (per `validateSitemapUrl`) are filtered out automatically. `
|
|
404
|
+
Converts a `SitemapURL[]` to the array format expected by Next.js App Router's `app/sitemap.ts` file convention. Invalid URLs (per `validateSitemapUrl`) are filtered out automatically. `changefreq` is mapped to `changeFrequency`.
|
|
399
405
|
|
|
400
|
-
| Field | Type | Description
|
|
401
|
-
| ----------------- | ---------------- |
|
|
402
|
-
| `url` | `string` | Absolute URL (`loc`)
|
|
403
|
-
| `lastModified` | `Date \| string` | From `lastmod` (
|
|
404
|
-
| `changeFrequency` | `string` | From `changefreq`
|
|
405
|
-
| `priority` | `number` | From `priority`
|
|
406
|
+
| Field | Type | Description |
|
|
407
|
+
| ----------------- | ---------------- | ------------------------------------- |
|
|
408
|
+
| `url` | `string` | Absolute URL (`loc`) |
|
|
409
|
+
| `lastModified` | `Date \| string` | From `lastmod` (passed through as-is) |
|
|
410
|
+
| `changeFrequency` | `string` | From `changefreq` |
|
|
411
|
+
| `priority` | `number` | From `priority` |
|
|
406
412
|
|
|
407
413
|
---
|
|
408
414
|
|
|
@@ -461,7 +467,7 @@ Converts a `SitemapURL[]` to the array format expected by Next.js App Router's `
|
|
|
461
467
|
- **SSR compatible** — safe to run in Next.js Server Components, Remix loaders, or Express handlers
|
|
462
468
|
- **Edge runtime safe** — no `fs`, no `path`, no Node.js-specific APIs; runs in Cloudflare Workers, Vercel Edge, Deno
|
|
463
469
|
- **Synchronous generator streaming** — `streamSitemap()` uses `function*` — no async overhead, no backpressure complexity
|
|
464
|
-
- **
|
|
470
|
+
- **Smart namespace detection** — `generateSitemap()` only declares image/video/news namespaces when actually used; `streamSitemap()` always includes all namespaces for simplicity
|
|
465
471
|
- **Tree-shakeable** — `"sideEffects": false` with named exports per function
|
|
466
472
|
- **Dual ESM + CJS** — ships both formats via tsup for any bundler or `require()` usage
|
|
467
473
|
|
|
@@ -472,7 +478,6 @@ Converts a `SitemapURL[]` to the array format expected by Next.js App Router's `
|
|
|
472
478
|
- No install scripts (`postinstall`, `preinstall`)
|
|
473
479
|
- No runtime network access
|
|
474
480
|
- No `eval` or dynamic code execution
|
|
475
|
-
- npm provenance enabled — every release is signed via Sigstore through GitHub Actions
|
|
476
481
|
- CI-signed builds — all releases published via verified `github.com/CyberCraftBD/power-seo` workflow
|
|
477
482
|
- Safe for SSR, Edge, and server environments
|
|
478
483
|
|
|
@@ -487,7 +492,7 @@ All 17 packages are independently installable — use only what you need.
|
|
|
487
492
|
| [`@power-seo/core`](https://www.npmjs.com/package/@power-seo/core) | `npm i @power-seo/core` | Framework-agnostic utilities, types, validators, and constants |
|
|
488
493
|
| [`@power-seo/react`](https://www.npmjs.com/package/@power-seo/react) | `npm i @power-seo/react` | React SEO components — meta, Open Graph, Twitter Card, breadcrumbs |
|
|
489
494
|
| [`@power-seo/meta`](https://www.npmjs.com/package/@power-seo/meta) | `npm i @power-seo/meta` | SSR meta helpers for Next.js App Router, Remix v2, and generic SSR |
|
|
490
|
-
| [`@power-seo/schema`](https://www.npmjs.com/package/@power-seo/schema) | `npm i @power-seo/schema` | Type-safe JSON-LD structured data — 23 builders +
|
|
495
|
+
| [`@power-seo/schema`](https://www.npmjs.com/package/@power-seo/schema) | `npm i @power-seo/schema` | Type-safe JSON-LD structured data — 23 builders + 22 React components |
|
|
491
496
|
| [`@power-seo/content-analysis`](https://www.npmjs.com/package/@power-seo/content-analysis) | `npm i @power-seo/content-analysis` | Yoast-style SEO content scoring engine with React components |
|
|
492
497
|
| [`@power-seo/readability`](https://www.npmjs.com/package/@power-seo/readability) | `npm i @power-seo/readability` | Readability scoring — Flesch-Kincaid, Gunning Fog, Coleman-Liau, ARI |
|
|
493
498
|
| [`@power-seo/preview`](https://www.npmjs.com/package/@power-seo/preview) | `npm i @power-seo/preview` | SERP, Open Graph, and Twitter/X Card preview generators |
|
|
@@ -504,12 +509,6 @@ All 17 packages are independently installable — use only what you need.
|
|
|
504
509
|
|
|
505
510
|
---
|
|
506
511
|
|
|
507
|
-
## Keywords
|
|
508
|
-
|
|
509
|
-
seo · sitemap · xml · xml sitemap · sitemap generator · sitemap index · nextjs sitemap · streaming sitemap · image sitemap · video sitemap · news sitemap · split sitemap · sitemap validation · edge runtime · remix · crawl budget · google sitemap · url priority · typescript · zero dependency
|
|
510
|
-
|
|
511
|
-
---
|
|
512
|
-
|
|
513
512
|
## About [CyberCraft Bangladesh](https://ccbd.dev)
|
|
514
513
|
|
|
515
514
|
**[CyberCraft Bangladesh](https://ccbd.dev)** is a Bangladesh-based enterprise-grade software development and Full Stack SEO service provider company specializing in ERP system development, AI-powered SaaS and business applications, full-stack SEO services, custom website development, and scalable eCommerce platforms. We design and develop intelligent, automation-driven SaaS and enterprise solutions that help startups, SMEs, NGOs, educational institutes, and large organizations streamline operations, enhance digital visibility, and accelerate growth through modern cloud-native technologies.
|
package/dist/index.cjs
CHANGED
|
@@ -298,7 +298,7 @@ function validateSitemapUrl(url) {
|
|
|
298
298
|
}
|
|
299
299
|
}
|
|
300
300
|
if (url.lastmod) {
|
|
301
|
-
const dateRegex = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2})?([+-]\d{2}:\d{2}|Z)?)?$/;
|
|
301
|
+
const dateRegex = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2}(\.\d{1,3})?)?([+-]\d{2}:\d{2}|Z)?)?$/;
|
|
302
302
|
if (!dateRegex.test(url.lastmod)) {
|
|
303
303
|
errors.push(
|
|
304
304
|
`"lastmod" value "${url.lastmod}" is not a valid W3C datetime format (YYYY-MM-DD or ISO 8601).`
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/generator.ts","../src/types.ts","../src/sitemap-index.ts","../src/stream.ts","../src/validate.ts","../src/next-adapter.ts"],"sourcesContent":["// @power-seo/sitemap — Public API\n// ----------------------------------------------------------------------------\n\nexport { generateSitemap } from './generator.js';\nexport { generateSitemapIndex, splitSitemap } from './sitemap-index.js';\nexport { streamSitemap } from './stream.js';\nexport { validateSitemapUrl } from './validate.js';\nexport { toNextSitemap } from './next-adapter.js';\nexport type { NextSitemapEntry } from './next-adapter.js';\n\nexport type {\n SitemapURL,\n SitemapImage,\n SitemapVideo,\n SitemapNews,\n SitemapConfig,\n SitemapIndexEntry,\n SitemapIndexConfig,\n SitemapValidationResult,\n} from './types.js';\n\nexport { MAX_URLS_PER_SITEMAP, MAX_SITEMAP_SIZE_BYTES } from './types.js';\n","// @power-seo/sitemap — XML Sitemap Generator\n// ----------------------------------------------------------------------------\n\nimport type {\n SitemapConfig,\n SitemapURL,\n SitemapImage,\n SitemapVideo,\n SitemapNews,\n} from '@power-seo/core';\nimport { normalizeUrl } from '@power-seo/core';\n\n/** Escape special XML characters. */\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\n/** Determine which namespace extensions are needed. */\nfunction detectNamespaces(urls: SitemapURL[]): { image: boolean; video: boolean; news: boolean } {\n let image = false;\n let video = false;\n let news = false;\n for (const url of urls) {\n if (url.images && url.images.length > 0) image = true;\n if (url.videos && url.videos.length > 0) video = true;\n if (url.news) news = true;\n if (image && video && news) break;\n }\n return { image, video, news };\n}\n\nfunction buildImageXml(images: SitemapImage[]): string {\n return images\n .map((img) => {\n let xml = ' <image:image>\\n';\n xml += ` <image:loc>${escapeXml(img.loc)}</image:loc>\\n`;\n if (img.caption) xml += ` <image:caption>${escapeXml(img.caption)}</image:caption>\\n`;\n if (img.geoLocation)\n xml += ` <image:geo_location>${escapeXml(img.geoLocation)}</image:geo_location>\\n`;\n if (img.title) xml += ` <image:title>${escapeXml(img.title)}</image:title>\\n`;\n if (img.license) xml += ` <image:license>${escapeXml(img.license)}</image:license>\\n`;\n xml += ' </image:image>\\n';\n return xml;\n })\n .join('');\n}\n\nfunction buildVideoXml(videos: SitemapVideo[]): string {\n return videos\n .map((vid) => {\n let xml = ' <video:video>\\n';\n xml += ` <video:thumbnail_loc>${escapeXml(vid.thumbnailLoc)}</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 if (vid.contentLoc)\n xml += ` <video:content_loc>${escapeXml(vid.contentLoc)}</video:content_loc>\\n`;\n if (vid.playerLoc)\n xml += ` <video:player_loc>${escapeXml(vid.playerLoc)}</video:player_loc>\\n`;\n if (vid.duration !== undefined)\n xml += ` <video:duration>${vid.duration}</video:duration>\\n`;\n if (vid.expirationDate)\n xml += ` <video:expiration_date>${escapeXml(vid.expirationDate)}</video:expiration_date>\\n`;\n if (vid.rating !== undefined) xml += ` <video:rating>${vid.rating}</video:rating>\\n`;\n if (vid.viewCount !== undefined)\n xml += ` <video:view_count>${vid.viewCount}</video:view_count>\\n`;\n if (vid.publicationDate)\n xml += ` <video:publication_date>${escapeXml(vid.publicationDate)}</video:publication_date>\\n`;\n if (vid.familyFriendly !== undefined)\n xml += ` <video:family_friendly>${vid.familyFriendly ? 'yes' : 'no'}</video:family_friendly>\\n`;\n if (vid.live !== undefined)\n xml += ` <video:live>${vid.live ? 'yes' : 'no'}</video:live>\\n`;\n xml += ' </video:video>\\n';\n return xml;\n })\n .join('');\n}\n\nfunction buildNewsXml(news: SitemapNews): string {\n let xml = ' <news:news>\\n';\n xml += ' <news:publication>\\n';\n xml += ` <news:name>${escapeXml(news.publication.name)}</news:name>\\n`;\n xml += ` <news:language>${escapeXml(news.publication.language)}</news:language>\\n`;\n xml += ' </news:publication>\\n';\n xml += ` <news:publication_date>${escapeXml(news.publicationDate)}</news:publication_date>\\n`;\n xml += ` <news:title>${escapeXml(news.title)}</news:title>\\n`;\n xml += ' </news:news>\\n';\n return xml;\n}\n\nfunction buildUrlXml(url: SitemapURL, hostname: string): string {\n const loc = url.loc.startsWith('http')\n ? url.loc\n : normalizeUrl(`${hostname}${url.loc.startsWith('/') ? '' : '/'}${url.loc}`);\n\n let xml = ' <url>\\n';\n xml += ` <loc>${escapeXml(loc)}</loc>\\n`;\n if (url.lastmod) xml += ` <lastmod>${escapeXml(url.lastmod)}</lastmod>\\n`;\n if (url.changefreq) xml += ` <changefreq>${url.changefreq}</changefreq>\\n`;\n if (url.priority !== undefined) xml += ` <priority>${url.priority.toFixed(1)}</priority>\\n`;\n if (url.images && url.images.length > 0) xml += buildImageXml(url.images);\n if (url.videos && url.videos.length > 0) xml += buildVideoXml(url.videos);\n if (url.news) xml += buildNewsXml(url.news);\n xml += ' </url>\\n';\n return xml;\n}\n\n/**\n * Generate an XML sitemap string from a sitemap configuration.\n *\n * @example\n * ```ts\n * const xml = generateSitemap({\n * hostname: 'https://example.com',\n * urls: [\n * { loc: '/', changefreq: 'daily', priority: 1.0 },\n * { loc: '/about', changefreq: 'monthly', priority: 0.8 },\n * ],\n * });\n * ```\n */\nexport function generateSitemap(config: SitemapConfig): string {\n const { hostname, urls } = config;\n const ns = detectNamespaces(urls);\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 if (ns.image) xml += '\\n xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\"';\n if (ns.video) xml += '\\n xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\"';\n if (ns.news) xml += '\\n xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\"';\n xml += '>\\n';\n\n for (const url of urls) {\n xml += buildUrlXml(url, hostname);\n }\n\n xml += '</urlset>\\n';\n return xml;\n}\n","// @power-seo/sitemap — Types\n// ----------------------------------------------------------------------------\n\nexport type {\n SitemapURL,\n SitemapImage,\n SitemapVideo,\n SitemapNews,\n SitemapConfig,\n} from '@power-seo/core';\n\n/** A sitemap entry in a sitemap index. */\nexport interface SitemapIndexEntry {\n loc: string;\n lastmod?: string;\n}\n\n/** Configuration for the sitemap index generator. */\nexport interface SitemapIndexConfig {\n sitemaps: SitemapIndexEntry[];\n}\n\n/** Result of URL validation. */\nexport interface SitemapValidationResult {\n valid: boolean;\n errors: string[];\n warnings: string[];\n}\n\n/** Maximum URLs allowed per sitemap file. */\nexport const MAX_URLS_PER_SITEMAP = 50_000 as const;\n\n/** Maximum sitemap file size in bytes (50MB). */\nexport const MAX_SITEMAP_SIZE_BYTES = 52_428_800 as const;\n","// @power-seo/sitemap — Sitemap Index Generator\n// ----------------------------------------------------------------------------\n\nimport type { SitemapConfig } from '@power-seo/core';\nimport type { SitemapIndexConfig, SitemapIndexEntry } from './types.js';\nimport { MAX_URLS_PER_SITEMAP } from './types.js';\nimport { generateSitemap } from './generator.js';\n\n/** Escape special XML characters. */\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\n/**\n * Generate a sitemap index XML string.\n *\n * @example\n * ```ts\n * const indexXml = generateSitemapIndex({\n * sitemaps: [\n * { loc: 'https://example.com/sitemap-0.xml', lastmod: '2024-01-01' },\n * { loc: 'https://example.com/sitemap-1.xml', lastmod: '2024-01-01' },\n * ],\n * });\n * ```\n */\nexport function generateSitemapIndex(config: SitemapIndexConfig): string {\n let xml = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n';\n xml += '<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\\n';\n\n for (const sitemap of config.sitemaps) {\n xml += ' <sitemap>\\n';\n xml += ` <loc>${escapeXml(sitemap.loc)}</loc>\\n`;\n if (sitemap.lastmod) {\n xml += ` <lastmod>${escapeXml(sitemap.lastmod)}</lastmod>\\n`;\n }\n xml += ' </sitemap>\\n';\n }\n\n xml += '</sitemapindex>\\n';\n return xml;\n}\n\n/**\n * Split a large sitemap config into multiple sitemaps + an index.\n *\n * When a site has more than 50,000 URLs, this function splits them into\n * multiple sitemap files and returns both the individual sitemaps and\n * the index that references them.\n *\n * @example\n * ```ts\n * const { index, sitemaps } = splitSitemap({\n * hostname: 'https://example.com',\n * urls: largeUrlArray,\n * });\n * // sitemaps[0].xml, sitemaps[1].xml, etc.\n * // index = sitemap index XML\n * ```\n */\nexport function splitSitemap(\n config: SitemapConfig,\n sitemapUrlPattern = '/sitemap-{index}.xml',\n): { index: string; sitemaps: Array<{ filename: string; xml: string }> } {\n const maxPerSitemap = config.maxUrlsPerSitemap ?? MAX_URLS_PER_SITEMAP;\n const { hostname, urls } = config;\n\n if (urls.length <= maxPerSitemap) {\n const xml = generateSitemap(config);\n const filename = sitemapUrlPattern.replace('{index}', '0');\n const indexEntries: SitemapIndexEntry[] = [{ loc: `${hostname}${filename}` }];\n return {\n index: generateSitemapIndex({ sitemaps: indexEntries }),\n sitemaps: [{ filename, xml }],\n };\n }\n\n const sitemaps: Array<{ filename: string; xml: string }> = [];\n const indexEntries: SitemapIndexEntry[] = [];\n\n for (let i = 0; i < urls.length; i += maxPerSitemap) {\n const chunk = urls.slice(i, i + maxPerSitemap);\n const chunkIndex = Math.floor(i / maxPerSitemap);\n const filename = sitemapUrlPattern.replace('{index}', String(chunkIndex));\n const xml = generateSitemap({ hostname, urls: chunk });\n\n sitemaps.push({ filename, xml });\n indexEntries.push({ loc: `${hostname}${filename}` });\n }\n\n return {\n index: generateSitemapIndex({ sitemaps: indexEntries }),\n sitemaps,\n };\n}\n","// @power-seo/sitemap — Streaming Sitemap Generator\n// ----------------------------------------------------------------------------\n\nimport type { SitemapURL } from '@power-seo/core';\nimport { normalizeUrl } from '@power-seo/core';\n\n/** Escape special XML characters. */\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\n/**\n * A streaming sitemap generator that yields XML chunks.\n *\n * Useful for server responses where you want to stream the sitemap\n * instead of building the entire XML string in memory.\n *\n * @example\n * ```ts\n * const stream = streamSitemap('https://example.com', urls);\n * for (const chunk of stream) {\n * response.write(chunk);\n * }\n * ```\n */\nexport function* streamSitemap(\n hostname: string,\n urls: Iterable<SitemapURL>,\n): Generator<string, void, undefined> {\n yield '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n';\n yield '<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\"\\n';\n yield ' xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\"\\n';\n yield ' xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\"\\n';\n yield ' xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\">\\n';\n\n for (const url of urls) {\n const loc = url.loc.startsWith('http')\n ? url.loc\n : normalizeUrl(`${hostname}${url.loc.startsWith('/') ? '' : '/'}${url.loc}`);\n\n yield ' <url>\\n';\n yield ` <loc>${escapeXml(loc)}</loc>\\n`;\n\n if (url.lastmod) yield ` <lastmod>${escapeXml(url.lastmod)}</lastmod>\\n`;\n if (url.changefreq) yield ` <changefreq>${url.changefreq}</changefreq>\\n`;\n if (url.priority !== undefined) yield ` <priority>${url.priority.toFixed(1)}</priority>\\n`;\n\n // Images\n if (url.images) {\n for (const img of url.images) {\n yield ' <image:image>\\n';\n yield ` <image:loc>${escapeXml(img.loc)}</image:loc>\\n`;\n if (img.caption) yield ` <image:caption>${escapeXml(img.caption)}</image:caption>\\n`;\n if (img.title) yield ` <image:title>${escapeXml(img.title)}</image:title>\\n`;\n yield ' </image:image>\\n';\n }\n }\n\n // Videos\n if (url.videos) {\n for (const vid of url.videos) {\n yield ' <video:video>\\n';\n yield ` <video:thumbnail_loc>${escapeXml(vid.thumbnailLoc)}</video:thumbnail_loc>\\n`;\n yield ` <video:title>${escapeXml(vid.title)}</video:title>\\n`;\n yield ` <video:description>${escapeXml(vid.description)}</video:description>\\n`;\n if (vid.contentLoc)\n yield ` <video:content_loc>${escapeXml(vid.contentLoc)}</video:content_loc>\\n`;\n if (vid.playerLoc)\n yield ` <video:player_loc>${escapeXml(vid.playerLoc)}</video:player_loc>\\n`;\n yield ' </video:video>\\n';\n }\n }\n\n // News\n if (url.news) {\n yield ' <news:news>\\n';\n yield ' <news:publication>\\n';\n yield ` <news:name>${escapeXml(url.news.publication.name)}</news:name>\\n`;\n yield ` <news:language>${escapeXml(url.news.publication.language)}</news:language>\\n`;\n yield ' </news:publication>\\n';\n yield ` <news:publication_date>${escapeXml(url.news.publicationDate)}</news:publication_date>\\n`;\n yield ` <news:title>${escapeXml(url.news.title)}</news:title>\\n`;\n yield ' </news:news>\\n';\n }\n\n yield ' </url>\\n';\n }\n\n yield '</urlset>\\n';\n}\n","// @power-seo/sitemap — URL Validation\n// ----------------------------------------------------------------------------\n\nimport type { SitemapURL } from '@power-seo/core';\nimport { isAbsoluteUrl, MAX_URL_LENGTH } from '@power-seo/core';\nimport type { SitemapValidationResult } from './types.js';\n\nconst VALID_CHANGEFREQ = ['always', 'hourly', 'daily', 'weekly', 'monthly', 'yearly', 'never'];\n\n/**\n * Validate a sitemap URL entry against the sitemap protocol spec.\n */\nexport function validateSitemapUrl(url: SitemapURL): SitemapValidationResult {\n const errors: string[] = [];\n const warnings: string[] = [];\n\n // loc is required\n if (!url.loc || url.loc.trim().length === 0) {\n errors.push('URL \"loc\" is required and cannot be empty.');\n } else {\n if (!isAbsoluteUrl(url.loc)) {\n errors.push(`URL \"${url.loc}\" must be an absolute URL (starting with http:// or https://).`);\n }\n\n if (url.loc.length > 2048) {\n errors.push(`URL \"${url.loc}\" exceeds the maximum length of 2048 characters.`);\n }\n\n if (url.loc.length > MAX_URL_LENGTH) {\n warnings.push(\n `URL \"${url.loc}\" is ${url.loc.length} characters. URLs under ${MAX_URL_LENGTH} characters are recommended for SEO.`,\n );\n }\n }\n\n // lastmod validation\n if (url.lastmod) {\n const dateRegex = /^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}(:\\d{2})?([+-]\\d{2}:\\d{2}|Z)?)?$/;\n if (!dateRegex.test(url.lastmod)) {\n errors.push(\n `\"lastmod\" value \"${url.lastmod}\" is not a valid W3C datetime format (YYYY-MM-DD or ISO 8601).`,\n );\n }\n }\n\n // changefreq validation\n if (url.changefreq && !VALID_CHANGEFREQ.includes(url.changefreq)) {\n errors.push(\n `\"changefreq\" value \"${url.changefreq}\" is invalid. Must be one of: ${VALID_CHANGEFREQ.join(', ')}.`,\n );\n }\n\n // priority validation\n if (url.priority !== undefined) {\n if (url.priority < 0 || url.priority > 1) {\n errors.push(`\"priority\" value ${url.priority} is out of range. Must be between 0.0 and 1.0.`);\n }\n }\n\n // Image validation\n if (url.images) {\n for (let i = 0; i < url.images.length; i++) {\n const img = url.images[i]!;\n if (!img.loc || img.loc.trim().length === 0) {\n errors.push(`Image ${i + 1}: \"loc\" is required.`);\n } else if (!isAbsoluteUrl(img.loc)) {\n errors.push(`Image ${i + 1}: \"${img.loc}\" must be an absolute URL.`);\n }\n }\n if (url.images.length > 1000) {\n warnings.push(\n `URL has ${url.images.length} images. Google supports up to 1,000 images per page.`,\n );\n }\n }\n\n // Video validation\n if (url.videos) {\n for (let i = 0; i < url.videos.length; i++) {\n const vid = url.videos[i]!;\n if (!vid.title) errors.push(`Video ${i + 1}: \"title\" is required.`);\n if (!vid.description) errors.push(`Video ${i + 1}: \"description\" is required.`);\n if (!vid.thumbnailLoc) errors.push(`Video ${i + 1}: \"thumbnailLoc\" is required.`);\n if (!vid.contentLoc && !vid.playerLoc) {\n errors.push(`Video ${i + 1}: either \"contentLoc\" or \"playerLoc\" must be provided.`);\n }\n if (vid.rating !== undefined && (vid.rating < 0 || vid.rating > 5)) {\n errors.push(`Video ${i + 1}: \"rating\" must be between 0.0 and 5.0.`);\n }\n }\n }\n\n // News validation\n if (url.news) {\n if (!url.news.publication?.name) errors.push('News: \"publication.name\" is required.');\n if (!url.news.publication?.language) errors.push('News: \"publication.language\" is required.');\n if (!url.news.publicationDate) errors.push('News: \"publicationDate\" is required.');\n if (!url.news.title) errors.push('News: \"title\" is required.');\n }\n\n return {\n valid: errors.length === 0,\n errors,\n warnings,\n };\n}\n","// @power-seo/sitemap — Next.js App Router Adapter\n\n\nimport type { SitemapURL } from '@power-seo/core';\nimport { validateSitemapUrl } from './validate.js';\n\n/** Plain object matching the shape of Next.js `MetadataRoute.Sitemap[number]`. */\nexport interface NextSitemapEntry {\n url: string;\n lastModified?: string | Date;\n changeFrequency?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';\n priority?: number;\n}\n\n/**\n * Convert `SitemapURL[]` to a plain array compatible with Next.js `MetadataRoute.Sitemap`.\n *\n * @example\n * ```ts\n * // app/sitemap.ts\n * import { toNextSitemap } from '@power-seo/sitemap';\n * export default async function sitemap() {\n * return toNextSitemap(urls) as MetadataRoute.Sitemap;\n * }\n * ```\n */\nexport function toNextSitemap(urls: SitemapURL[]): NextSitemapEntry[] {\n const entries: NextSitemapEntry[] = [];\n for (const url of urls) {\n const { valid } = validateSitemapUrl(url);\n if (!valid) continue;\n const entry: NextSitemapEntry = { url: url.loc };\n if (url.lastmod) entry.lastModified = url.lastmod;\n if (url.changefreq) entry.changeFrequency = url.changefreq as NextSitemapEntry['changeFrequency'];\n if (url.priority !== undefined) entry.priority = url.priority;\n entries.push(entry);\n }\n return entries;\n}\n"],"mappings":"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","names":["escapeXml","indexEntries","import_core","escapeXml","import_core"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/generator.ts","../src/types.ts","../src/sitemap-index.ts","../src/stream.ts","../src/validate.ts","../src/next-adapter.ts"],"sourcesContent":["// @power-seo/sitemap — Public API\n// ----------------------------------------------------------------------------\n\nexport { generateSitemap } from './generator.js';\nexport { generateSitemapIndex, splitSitemap } from './sitemap-index.js';\nexport { streamSitemap } from './stream.js';\nexport { validateSitemapUrl } from './validate.js';\nexport { toNextSitemap } from './next-adapter.js';\nexport type { NextSitemapEntry } from './next-adapter.js';\n\nexport type {\n SitemapURL,\n SitemapImage,\n SitemapVideo,\n SitemapNews,\n SitemapConfig,\n SitemapIndexEntry,\n SitemapIndexConfig,\n SitemapValidationResult,\n} from './types.js';\n\nexport { MAX_URLS_PER_SITEMAP, MAX_SITEMAP_SIZE_BYTES } from './types.js';\n","// @power-seo/sitemap — XML Sitemap Generator\n// ----------------------------------------------------------------------------\n\nimport type {\n SitemapConfig,\n SitemapURL,\n SitemapImage,\n SitemapVideo,\n SitemapNews,\n} from '@power-seo/core';\nimport { normalizeUrl } from '@power-seo/core';\n\n/** Escape special XML characters. */\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\n/** Determine which namespace extensions are needed. */\nfunction detectNamespaces(urls: SitemapURL[]): { image: boolean; video: boolean; news: boolean } {\n let image = false;\n let video = false;\n let news = false;\n for (const url of urls) {\n if (url.images && url.images.length > 0) image = true;\n if (url.videos && url.videos.length > 0) video = true;\n if (url.news) news = true;\n if (image && video && news) break;\n }\n return { image, video, news };\n}\n\nfunction buildImageXml(images: SitemapImage[]): string {\n return images\n .map((img) => {\n let xml = ' <image:image>\\n';\n xml += ` <image:loc>${escapeXml(img.loc)}</image:loc>\\n`;\n if (img.caption) xml += ` <image:caption>${escapeXml(img.caption)}</image:caption>\\n`;\n if (img.geoLocation)\n xml += ` <image:geo_location>${escapeXml(img.geoLocation)}</image:geo_location>\\n`;\n if (img.title) xml += ` <image:title>${escapeXml(img.title)}</image:title>\\n`;\n if (img.license) xml += ` <image:license>${escapeXml(img.license)}</image:license>\\n`;\n xml += ' </image:image>\\n';\n return xml;\n })\n .join('');\n}\n\nfunction buildVideoXml(videos: SitemapVideo[]): string {\n return videos\n .map((vid) => {\n let xml = ' <video:video>\\n';\n xml += ` <video:thumbnail_loc>${escapeXml(vid.thumbnailLoc)}</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 if (vid.contentLoc)\n xml += ` <video:content_loc>${escapeXml(vid.contentLoc)}</video:content_loc>\\n`;\n if (vid.playerLoc)\n xml += ` <video:player_loc>${escapeXml(vid.playerLoc)}</video:player_loc>\\n`;\n if (vid.duration !== undefined)\n xml += ` <video:duration>${vid.duration}</video:duration>\\n`;\n if (vid.expirationDate)\n xml += ` <video:expiration_date>${escapeXml(vid.expirationDate)}</video:expiration_date>\\n`;\n if (vid.rating !== undefined) xml += ` <video:rating>${vid.rating}</video:rating>\\n`;\n if (vid.viewCount !== undefined)\n xml += ` <video:view_count>${vid.viewCount}</video:view_count>\\n`;\n if (vid.publicationDate)\n xml += ` <video:publication_date>${escapeXml(vid.publicationDate)}</video:publication_date>\\n`;\n if (vid.familyFriendly !== undefined)\n xml += ` <video:family_friendly>${vid.familyFriendly ? 'yes' : 'no'}</video:family_friendly>\\n`;\n if (vid.live !== undefined)\n xml += ` <video:live>${vid.live ? 'yes' : 'no'}</video:live>\\n`;\n xml += ' </video:video>\\n';\n return xml;\n })\n .join('');\n}\n\nfunction buildNewsXml(news: SitemapNews): string {\n let xml = ' <news:news>\\n';\n xml += ' <news:publication>\\n';\n xml += ` <news:name>${escapeXml(news.publication.name)}</news:name>\\n`;\n xml += ` <news:language>${escapeXml(news.publication.language)}</news:language>\\n`;\n xml += ' </news:publication>\\n';\n xml += ` <news:publication_date>${escapeXml(news.publicationDate)}</news:publication_date>\\n`;\n xml += ` <news:title>${escapeXml(news.title)}</news:title>\\n`;\n xml += ' </news:news>\\n';\n return xml;\n}\n\nfunction buildUrlXml(url: SitemapURL, hostname: string): string {\n const loc = url.loc.startsWith('http')\n ? url.loc\n : normalizeUrl(`${hostname}${url.loc.startsWith('/') ? '' : '/'}${url.loc}`);\n\n let xml = ' <url>\\n';\n xml += ` <loc>${escapeXml(loc)}</loc>\\n`;\n if (url.lastmod) xml += ` <lastmod>${escapeXml(url.lastmod)}</lastmod>\\n`;\n if (url.changefreq) xml += ` <changefreq>${url.changefreq}</changefreq>\\n`;\n if (url.priority !== undefined) xml += ` <priority>${url.priority.toFixed(1)}</priority>\\n`;\n if (url.images && url.images.length > 0) xml += buildImageXml(url.images);\n if (url.videos && url.videos.length > 0) xml += buildVideoXml(url.videos);\n if (url.news) xml += buildNewsXml(url.news);\n xml += ' </url>\\n';\n return xml;\n}\n\n/**\n * Generate an XML sitemap string from a sitemap configuration.\n *\n * @example\n * ```ts\n * const xml = generateSitemap({\n * hostname: 'https://example.com',\n * urls: [\n * { loc: '/', changefreq: 'daily', priority: 1.0 },\n * { loc: '/about', changefreq: 'monthly', priority: 0.8 },\n * ],\n * });\n * ```\n */\nexport function generateSitemap(config: SitemapConfig): string {\n const { hostname, urls } = config;\n const ns = detectNamespaces(urls);\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 if (ns.image) xml += '\\n xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\"';\n if (ns.video) xml += '\\n xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\"';\n if (ns.news) xml += '\\n xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\"';\n xml += '>\\n';\n\n for (const url of urls) {\n xml += buildUrlXml(url, hostname);\n }\n\n xml += '</urlset>\\n';\n return xml;\n}\n","// @power-seo/sitemap — Types\n// ----------------------------------------------------------------------------\n\nexport type {\n SitemapURL,\n SitemapImage,\n SitemapVideo,\n SitemapNews,\n SitemapConfig,\n} from '@power-seo/core';\n\n/** A sitemap entry in a sitemap index. */\nexport interface SitemapIndexEntry {\n loc: string;\n lastmod?: string;\n}\n\n/** Configuration for the sitemap index generator. */\nexport interface SitemapIndexConfig {\n sitemaps: SitemapIndexEntry[];\n}\n\n/** Result of URL validation. */\nexport interface SitemapValidationResult {\n valid: boolean;\n errors: string[];\n warnings: string[];\n}\n\n/** Maximum URLs allowed per sitemap file. */\nexport const MAX_URLS_PER_SITEMAP = 50_000 as const;\n\n/** Maximum sitemap file size in bytes (50MB). */\nexport const MAX_SITEMAP_SIZE_BYTES = 52_428_800 as const;\n","// @power-seo/sitemap — Sitemap Index Generator\n// ----------------------------------------------------------------------------\n\nimport type { SitemapConfig } from '@power-seo/core';\nimport type { SitemapIndexConfig, SitemapIndexEntry } from './types.js';\nimport { MAX_URLS_PER_SITEMAP } from './types.js';\nimport { generateSitemap } from './generator.js';\n\n/** Escape special XML characters. */\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\n/**\n * Generate a sitemap index XML string.\n *\n * @example\n * ```ts\n * const indexXml = generateSitemapIndex({\n * sitemaps: [\n * { loc: 'https://example.com/sitemap-0.xml', lastmod: '2024-01-01' },\n * { loc: 'https://example.com/sitemap-1.xml', lastmod: '2024-01-01' },\n * ],\n * });\n * ```\n */\nexport function generateSitemapIndex(config: SitemapIndexConfig): string {\n let xml = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n';\n xml += '<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\\n';\n\n for (const sitemap of config.sitemaps) {\n xml += ' <sitemap>\\n';\n xml += ` <loc>${escapeXml(sitemap.loc)}</loc>\\n`;\n if (sitemap.lastmod) {\n xml += ` <lastmod>${escapeXml(sitemap.lastmod)}</lastmod>\\n`;\n }\n xml += ' </sitemap>\\n';\n }\n\n xml += '</sitemapindex>\\n';\n return xml;\n}\n\n/**\n * Split a large sitemap config into multiple sitemaps + an index.\n *\n * When a site has more than 50,000 URLs, this function splits them into\n * multiple sitemap files and returns both the individual sitemaps and\n * the index that references them.\n *\n * @example\n * ```ts\n * const { index, sitemaps } = splitSitemap({\n * hostname: 'https://example.com',\n * urls: largeUrlArray,\n * });\n * // sitemaps[0].xml, sitemaps[1].xml, etc.\n * // index = sitemap index XML\n * ```\n */\nexport function splitSitemap(\n config: SitemapConfig,\n sitemapUrlPattern = '/sitemap-{index}.xml',\n): { index: string; sitemaps: Array<{ filename: string; xml: string }> } {\n const maxPerSitemap = config.maxUrlsPerSitemap ?? MAX_URLS_PER_SITEMAP;\n const { hostname, urls } = config;\n\n if (urls.length <= maxPerSitemap) {\n const xml = generateSitemap(config);\n const filename = sitemapUrlPattern.replace('{index}', '0');\n const indexEntries: SitemapIndexEntry[] = [{ loc: `${hostname}${filename}` }];\n return {\n index: generateSitemapIndex({ sitemaps: indexEntries }),\n sitemaps: [{ filename, xml }],\n };\n }\n\n const sitemaps: Array<{ filename: string; xml: string }> = [];\n const indexEntries: SitemapIndexEntry[] = [];\n\n for (let i = 0; i < urls.length; i += maxPerSitemap) {\n const chunk = urls.slice(i, i + maxPerSitemap);\n const chunkIndex = Math.floor(i / maxPerSitemap);\n const filename = sitemapUrlPattern.replace('{index}', String(chunkIndex));\n const xml = generateSitemap({ hostname, urls: chunk });\n\n sitemaps.push({ filename, xml });\n indexEntries.push({ loc: `${hostname}${filename}` });\n }\n\n return {\n index: generateSitemapIndex({ sitemaps: indexEntries }),\n sitemaps,\n };\n}\n","// @power-seo/sitemap — Streaming Sitemap Generator\n// ----------------------------------------------------------------------------\n\nimport type { SitemapURL } from '@power-seo/core';\nimport { normalizeUrl } from '@power-seo/core';\n\n/** Escape special XML characters. */\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\n/**\n * A streaming sitemap generator that yields XML chunks.\n *\n * Useful for server responses where you want to stream the sitemap\n * instead of building the entire XML string in memory.\n *\n * @example\n * ```ts\n * const stream = streamSitemap('https://example.com', urls);\n * for (const chunk of stream) {\n * response.write(chunk);\n * }\n * ```\n */\nexport function* streamSitemap(\n hostname: string,\n urls: Iterable<SitemapURL>,\n): Generator<string, void, undefined> {\n yield '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n';\n yield '<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\"\\n';\n yield ' xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\"\\n';\n yield ' xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\"\\n';\n yield ' xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\">\\n';\n\n for (const url of urls) {\n const loc = url.loc.startsWith('http')\n ? url.loc\n : normalizeUrl(`${hostname}${url.loc.startsWith('/') ? '' : '/'}${url.loc}`);\n\n yield ' <url>\\n';\n yield ` <loc>${escapeXml(loc)}</loc>\\n`;\n\n if (url.lastmod) yield ` <lastmod>${escapeXml(url.lastmod)}</lastmod>\\n`;\n if (url.changefreq) yield ` <changefreq>${url.changefreq}</changefreq>\\n`;\n if (url.priority !== undefined) yield ` <priority>${url.priority.toFixed(1)}</priority>\\n`;\n\n // Images\n if (url.images) {\n for (const img of url.images) {\n yield ' <image:image>\\n';\n yield ` <image:loc>${escapeXml(img.loc)}</image:loc>\\n`;\n if (img.caption) yield ` <image:caption>${escapeXml(img.caption)}</image:caption>\\n`;\n if (img.title) yield ` <image:title>${escapeXml(img.title)}</image:title>\\n`;\n yield ' </image:image>\\n';\n }\n }\n\n // Videos\n if (url.videos) {\n for (const vid of url.videos) {\n yield ' <video:video>\\n';\n yield ` <video:thumbnail_loc>${escapeXml(vid.thumbnailLoc)}</video:thumbnail_loc>\\n`;\n yield ` <video:title>${escapeXml(vid.title)}</video:title>\\n`;\n yield ` <video:description>${escapeXml(vid.description)}</video:description>\\n`;\n if (vid.contentLoc)\n yield ` <video:content_loc>${escapeXml(vid.contentLoc)}</video:content_loc>\\n`;\n if (vid.playerLoc)\n yield ` <video:player_loc>${escapeXml(vid.playerLoc)}</video:player_loc>\\n`;\n yield ' </video:video>\\n';\n }\n }\n\n // News\n if (url.news) {\n yield ' <news:news>\\n';\n yield ' <news:publication>\\n';\n yield ` <news:name>${escapeXml(url.news.publication.name)}</news:name>\\n`;\n yield ` <news:language>${escapeXml(url.news.publication.language)}</news:language>\\n`;\n yield ' </news:publication>\\n';\n yield ` <news:publication_date>${escapeXml(url.news.publicationDate)}</news:publication_date>\\n`;\n yield ` <news:title>${escapeXml(url.news.title)}</news:title>\\n`;\n yield ' </news:news>\\n';\n }\n\n yield ' </url>\\n';\n }\n\n yield '</urlset>\\n';\n}\n","// @power-seo/sitemap — URL Validation\n// ----------------------------------------------------------------------------\n\nimport type { SitemapURL } from '@power-seo/core';\nimport { isAbsoluteUrl, MAX_URL_LENGTH } from '@power-seo/core';\nimport type { SitemapValidationResult } from './types.js';\n\nconst VALID_CHANGEFREQ = ['always', 'hourly', 'daily', 'weekly', 'monthly', 'yearly', 'never'];\n\n/**\n * Validate a sitemap URL entry against the sitemap protocol spec.\n */\nexport function validateSitemapUrl(url: SitemapURL): SitemapValidationResult {\n const errors: string[] = [];\n const warnings: string[] = [];\n\n // loc is required\n if (!url.loc || url.loc.trim().length === 0) {\n errors.push('URL \"loc\" is required and cannot be empty.');\n } else {\n if (!isAbsoluteUrl(url.loc)) {\n errors.push(`URL \"${url.loc}\" must be an absolute URL (starting with http:// or https://).`);\n }\n\n if (url.loc.length > 2048) {\n errors.push(`URL \"${url.loc}\" exceeds the maximum length of 2048 characters.`);\n }\n\n if (url.loc.length > MAX_URL_LENGTH) {\n warnings.push(\n `URL \"${url.loc}\" is ${url.loc.length} characters. URLs under ${MAX_URL_LENGTH} characters are recommended for SEO.`,\n );\n }\n }\n\n // lastmod validation\n if (url.lastmod) {\n const dateRegex = /^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}(:\\d{2}(\\.\\d{1,3})?)?([+-]\\d{2}:\\d{2}|Z)?)?$/;\n if (!dateRegex.test(url.lastmod)) {\n errors.push(\n `\"lastmod\" value \"${url.lastmod}\" is not a valid W3C datetime format (YYYY-MM-DD or ISO 8601).`,\n );\n }\n }\n\n // changefreq validation\n if (url.changefreq && !VALID_CHANGEFREQ.includes(url.changefreq)) {\n errors.push(\n `\"changefreq\" value \"${url.changefreq}\" is invalid. Must be one of: ${VALID_CHANGEFREQ.join(', ')}.`,\n );\n }\n\n // priority validation\n if (url.priority !== undefined) {\n if (url.priority < 0 || url.priority > 1) {\n errors.push(`\"priority\" value ${url.priority} is out of range. Must be between 0.0 and 1.0.`);\n }\n }\n\n // Image validation\n if (url.images) {\n for (let i = 0; i < url.images.length; i++) {\n const img = url.images[i]!;\n if (!img.loc || img.loc.trim().length === 0) {\n errors.push(`Image ${i + 1}: \"loc\" is required.`);\n } else if (!isAbsoluteUrl(img.loc)) {\n errors.push(`Image ${i + 1}: \"${img.loc}\" must be an absolute URL.`);\n }\n }\n if (url.images.length > 1000) {\n warnings.push(\n `URL has ${url.images.length} images. Google supports up to 1,000 images per page.`,\n );\n }\n }\n\n // Video validation\n if (url.videos) {\n for (let i = 0; i < url.videos.length; i++) {\n const vid = url.videos[i]!;\n if (!vid.title) errors.push(`Video ${i + 1}: \"title\" is required.`);\n if (!vid.description) errors.push(`Video ${i + 1}: \"description\" is required.`);\n if (!vid.thumbnailLoc) errors.push(`Video ${i + 1}: \"thumbnailLoc\" is required.`);\n if (!vid.contentLoc && !vid.playerLoc) {\n errors.push(`Video ${i + 1}: either \"contentLoc\" or \"playerLoc\" must be provided.`);\n }\n if (vid.rating !== undefined && (vid.rating < 0 || vid.rating > 5)) {\n errors.push(`Video ${i + 1}: \"rating\" must be between 0.0 and 5.0.`);\n }\n }\n }\n\n // News validation\n if (url.news) {\n if (!url.news.publication?.name) errors.push('News: \"publication.name\" is required.');\n if (!url.news.publication?.language) errors.push('News: \"publication.language\" is required.');\n if (!url.news.publicationDate) errors.push('News: \"publicationDate\" is required.');\n if (!url.news.title) errors.push('News: \"title\" is required.');\n }\n\n return {\n valid: errors.length === 0,\n errors,\n warnings,\n };\n}\n","// @power-seo/sitemap — Next.js App Router Adapter\n\n\nimport type { SitemapURL } from '@power-seo/core';\nimport { validateSitemapUrl } from './validate.js';\n\n/** Plain object matching the shape of Next.js `MetadataRoute.Sitemap[number]`. */\nexport interface NextSitemapEntry {\n url: string;\n lastModified?: string | Date;\n changeFrequency?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';\n priority?: number;\n}\n\n/**\n * Convert `SitemapURL[]` to a plain array compatible with Next.js `MetadataRoute.Sitemap`.\n *\n * @example\n * ```ts\n * // app/sitemap.ts\n * import { toNextSitemap } from '@power-seo/sitemap';\n * export default async function sitemap() {\n * return toNextSitemap(urls) as MetadataRoute.Sitemap;\n * }\n * ```\n */\nexport function toNextSitemap(urls: SitemapURL[]): NextSitemapEntry[] {\n const entries: NextSitemapEntry[] = [];\n for (const url of urls) {\n const { valid } = validateSitemapUrl(url);\n if (!valid) continue;\n const entry: NextSitemapEntry = { url: url.loc };\n if (url.lastmod) entry.lastModified = url.lastmod;\n if (url.changefreq) entry.changeFrequency = url.changefreq as NextSitemapEntry['changeFrequency'];\n if (url.priority !== undefined) entry.priority = url.priority;\n entries.push(entry);\n }\n return entries;\n}\n"],"mappings":"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","names":["escapeXml","indexEntries","import_core","escapeXml","import_core"]}
|
package/dist/index.js
CHANGED
|
@@ -265,7 +265,7 @@ function validateSitemapUrl(url) {
|
|
|
265
265
|
}
|
|
266
266
|
}
|
|
267
267
|
if (url.lastmod) {
|
|
268
|
-
const dateRegex = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2})?([+-]\d{2}:\d{2}|Z)?)?$/;
|
|
268
|
+
const dateRegex = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2}(\.\d{1,3})?)?([+-]\d{2}:\d{2}|Z)?)?$/;
|
|
269
269
|
if (!dateRegex.test(url.lastmod)) {
|
|
270
270
|
errors.push(
|
|
271
271
|
`"lastmod" value "${url.lastmod}" is not a valid W3C datetime format (YYYY-MM-DD or ISO 8601).`
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/generator.ts","../src/types.ts","../src/sitemap-index.ts","../src/stream.ts","../src/validate.ts","../src/next-adapter.ts"],"sourcesContent":["// @power-seo/sitemap — XML Sitemap Generator\n// ----------------------------------------------------------------------------\n\nimport type {\n SitemapConfig,\n SitemapURL,\n SitemapImage,\n SitemapVideo,\n SitemapNews,\n} from '@power-seo/core';\nimport { normalizeUrl } from '@power-seo/core';\n\n/** Escape special XML characters. */\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\n/** Determine which namespace extensions are needed. */\nfunction detectNamespaces(urls: SitemapURL[]): { image: boolean; video: boolean; news: boolean } {\n let image = false;\n let video = false;\n let news = false;\n for (const url of urls) {\n if (url.images && url.images.length > 0) image = true;\n if (url.videos && url.videos.length > 0) video = true;\n if (url.news) news = true;\n if (image && video && news) break;\n }\n return { image, video, news };\n}\n\nfunction buildImageXml(images: SitemapImage[]): string {\n return images\n .map((img) => {\n let xml = ' <image:image>\\n';\n xml += ` <image:loc>${escapeXml(img.loc)}</image:loc>\\n`;\n if (img.caption) xml += ` <image:caption>${escapeXml(img.caption)}</image:caption>\\n`;\n if (img.geoLocation)\n xml += ` <image:geo_location>${escapeXml(img.geoLocation)}</image:geo_location>\\n`;\n if (img.title) xml += ` <image:title>${escapeXml(img.title)}</image:title>\\n`;\n if (img.license) xml += ` <image:license>${escapeXml(img.license)}</image:license>\\n`;\n xml += ' </image:image>\\n';\n return xml;\n })\n .join('');\n}\n\nfunction buildVideoXml(videos: SitemapVideo[]): string {\n return videos\n .map((vid) => {\n let xml = ' <video:video>\\n';\n xml += ` <video:thumbnail_loc>${escapeXml(vid.thumbnailLoc)}</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 if (vid.contentLoc)\n xml += ` <video:content_loc>${escapeXml(vid.contentLoc)}</video:content_loc>\\n`;\n if (vid.playerLoc)\n xml += ` <video:player_loc>${escapeXml(vid.playerLoc)}</video:player_loc>\\n`;\n if (vid.duration !== undefined)\n xml += ` <video:duration>${vid.duration}</video:duration>\\n`;\n if (vid.expirationDate)\n xml += ` <video:expiration_date>${escapeXml(vid.expirationDate)}</video:expiration_date>\\n`;\n if (vid.rating !== undefined) xml += ` <video:rating>${vid.rating}</video:rating>\\n`;\n if (vid.viewCount !== undefined)\n xml += ` <video:view_count>${vid.viewCount}</video:view_count>\\n`;\n if (vid.publicationDate)\n xml += ` <video:publication_date>${escapeXml(vid.publicationDate)}</video:publication_date>\\n`;\n if (vid.familyFriendly !== undefined)\n xml += ` <video:family_friendly>${vid.familyFriendly ? 'yes' : 'no'}</video:family_friendly>\\n`;\n if (vid.live !== undefined)\n xml += ` <video:live>${vid.live ? 'yes' : 'no'}</video:live>\\n`;\n xml += ' </video:video>\\n';\n return xml;\n })\n .join('');\n}\n\nfunction buildNewsXml(news: SitemapNews): string {\n let xml = ' <news:news>\\n';\n xml += ' <news:publication>\\n';\n xml += ` <news:name>${escapeXml(news.publication.name)}</news:name>\\n`;\n xml += ` <news:language>${escapeXml(news.publication.language)}</news:language>\\n`;\n xml += ' </news:publication>\\n';\n xml += ` <news:publication_date>${escapeXml(news.publicationDate)}</news:publication_date>\\n`;\n xml += ` <news:title>${escapeXml(news.title)}</news:title>\\n`;\n xml += ' </news:news>\\n';\n return xml;\n}\n\nfunction buildUrlXml(url: SitemapURL, hostname: string): string {\n const loc = url.loc.startsWith('http')\n ? url.loc\n : normalizeUrl(`${hostname}${url.loc.startsWith('/') ? '' : '/'}${url.loc}`);\n\n let xml = ' <url>\\n';\n xml += ` <loc>${escapeXml(loc)}</loc>\\n`;\n if (url.lastmod) xml += ` <lastmod>${escapeXml(url.lastmod)}</lastmod>\\n`;\n if (url.changefreq) xml += ` <changefreq>${url.changefreq}</changefreq>\\n`;\n if (url.priority !== undefined) xml += ` <priority>${url.priority.toFixed(1)}</priority>\\n`;\n if (url.images && url.images.length > 0) xml += buildImageXml(url.images);\n if (url.videos && url.videos.length > 0) xml += buildVideoXml(url.videos);\n if (url.news) xml += buildNewsXml(url.news);\n xml += ' </url>\\n';\n return xml;\n}\n\n/**\n * Generate an XML sitemap string from a sitemap configuration.\n *\n * @example\n * ```ts\n * const xml = generateSitemap({\n * hostname: 'https://example.com',\n * urls: [\n * { loc: '/', changefreq: 'daily', priority: 1.0 },\n * { loc: '/about', changefreq: 'monthly', priority: 0.8 },\n * ],\n * });\n * ```\n */\nexport function generateSitemap(config: SitemapConfig): string {\n const { hostname, urls } = config;\n const ns = detectNamespaces(urls);\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 if (ns.image) xml += '\\n xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\"';\n if (ns.video) xml += '\\n xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\"';\n if (ns.news) xml += '\\n xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\"';\n xml += '>\\n';\n\n for (const url of urls) {\n xml += buildUrlXml(url, hostname);\n }\n\n xml += '</urlset>\\n';\n return xml;\n}\n","// @power-seo/sitemap — Types\n// ----------------------------------------------------------------------------\n\nexport type {\n SitemapURL,\n SitemapImage,\n SitemapVideo,\n SitemapNews,\n SitemapConfig,\n} from '@power-seo/core';\n\n/** A sitemap entry in a sitemap index. */\nexport interface SitemapIndexEntry {\n loc: string;\n lastmod?: string;\n}\n\n/** Configuration for the sitemap index generator. */\nexport interface SitemapIndexConfig {\n sitemaps: SitemapIndexEntry[];\n}\n\n/** Result of URL validation. */\nexport interface SitemapValidationResult {\n valid: boolean;\n errors: string[];\n warnings: string[];\n}\n\n/** Maximum URLs allowed per sitemap file. */\nexport const MAX_URLS_PER_SITEMAP = 50_000 as const;\n\n/** Maximum sitemap file size in bytes (50MB). */\nexport const MAX_SITEMAP_SIZE_BYTES = 52_428_800 as const;\n","// @power-seo/sitemap — Sitemap Index Generator\n// ----------------------------------------------------------------------------\n\nimport type { SitemapConfig } from '@power-seo/core';\nimport type { SitemapIndexConfig, SitemapIndexEntry } from './types.js';\nimport { MAX_URLS_PER_SITEMAP } from './types.js';\nimport { generateSitemap } from './generator.js';\n\n/** Escape special XML characters. */\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\n/**\n * Generate a sitemap index XML string.\n *\n * @example\n * ```ts\n * const indexXml = generateSitemapIndex({\n * sitemaps: [\n * { loc: 'https://example.com/sitemap-0.xml', lastmod: '2024-01-01' },\n * { loc: 'https://example.com/sitemap-1.xml', lastmod: '2024-01-01' },\n * ],\n * });\n * ```\n */\nexport function generateSitemapIndex(config: SitemapIndexConfig): string {\n let xml = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n';\n xml += '<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\\n';\n\n for (const sitemap of config.sitemaps) {\n xml += ' <sitemap>\\n';\n xml += ` <loc>${escapeXml(sitemap.loc)}</loc>\\n`;\n if (sitemap.lastmod) {\n xml += ` <lastmod>${escapeXml(sitemap.lastmod)}</lastmod>\\n`;\n }\n xml += ' </sitemap>\\n';\n }\n\n xml += '</sitemapindex>\\n';\n return xml;\n}\n\n/**\n * Split a large sitemap config into multiple sitemaps + an index.\n *\n * When a site has more than 50,000 URLs, this function splits them into\n * multiple sitemap files and returns both the individual sitemaps and\n * the index that references them.\n *\n * @example\n * ```ts\n * const { index, sitemaps } = splitSitemap({\n * hostname: 'https://example.com',\n * urls: largeUrlArray,\n * });\n * // sitemaps[0].xml, sitemaps[1].xml, etc.\n * // index = sitemap index XML\n * ```\n */\nexport function splitSitemap(\n config: SitemapConfig,\n sitemapUrlPattern = '/sitemap-{index}.xml',\n): { index: string; sitemaps: Array<{ filename: string; xml: string }> } {\n const maxPerSitemap = config.maxUrlsPerSitemap ?? MAX_URLS_PER_SITEMAP;\n const { hostname, urls } = config;\n\n if (urls.length <= maxPerSitemap) {\n const xml = generateSitemap(config);\n const filename = sitemapUrlPattern.replace('{index}', '0');\n const indexEntries: SitemapIndexEntry[] = [{ loc: `${hostname}${filename}` }];\n return {\n index: generateSitemapIndex({ sitemaps: indexEntries }),\n sitemaps: [{ filename, xml }],\n };\n }\n\n const sitemaps: Array<{ filename: string; xml: string }> = [];\n const indexEntries: SitemapIndexEntry[] = [];\n\n for (let i = 0; i < urls.length; i += maxPerSitemap) {\n const chunk = urls.slice(i, i + maxPerSitemap);\n const chunkIndex = Math.floor(i / maxPerSitemap);\n const filename = sitemapUrlPattern.replace('{index}', String(chunkIndex));\n const xml = generateSitemap({ hostname, urls: chunk });\n\n sitemaps.push({ filename, xml });\n indexEntries.push({ loc: `${hostname}${filename}` });\n }\n\n return {\n index: generateSitemapIndex({ sitemaps: indexEntries }),\n sitemaps,\n };\n}\n","// @power-seo/sitemap — Streaming Sitemap Generator\n// ----------------------------------------------------------------------------\n\nimport type { SitemapURL } from '@power-seo/core';\nimport { normalizeUrl } from '@power-seo/core';\n\n/** Escape special XML characters. */\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\n/**\n * A streaming sitemap generator that yields XML chunks.\n *\n * Useful for server responses where you want to stream the sitemap\n * instead of building the entire XML string in memory.\n *\n * @example\n * ```ts\n * const stream = streamSitemap('https://example.com', urls);\n * for (const chunk of stream) {\n * response.write(chunk);\n * }\n * ```\n */\nexport function* streamSitemap(\n hostname: string,\n urls: Iterable<SitemapURL>,\n): Generator<string, void, undefined> {\n yield '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n';\n yield '<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\"\\n';\n yield ' xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\"\\n';\n yield ' xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\"\\n';\n yield ' xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\">\\n';\n\n for (const url of urls) {\n const loc = url.loc.startsWith('http')\n ? url.loc\n : normalizeUrl(`${hostname}${url.loc.startsWith('/') ? '' : '/'}${url.loc}`);\n\n yield ' <url>\\n';\n yield ` <loc>${escapeXml(loc)}</loc>\\n`;\n\n if (url.lastmod) yield ` <lastmod>${escapeXml(url.lastmod)}</lastmod>\\n`;\n if (url.changefreq) yield ` <changefreq>${url.changefreq}</changefreq>\\n`;\n if (url.priority !== undefined) yield ` <priority>${url.priority.toFixed(1)}</priority>\\n`;\n\n // Images\n if (url.images) {\n for (const img of url.images) {\n yield ' <image:image>\\n';\n yield ` <image:loc>${escapeXml(img.loc)}</image:loc>\\n`;\n if (img.caption) yield ` <image:caption>${escapeXml(img.caption)}</image:caption>\\n`;\n if (img.title) yield ` <image:title>${escapeXml(img.title)}</image:title>\\n`;\n yield ' </image:image>\\n';\n }\n }\n\n // Videos\n if (url.videos) {\n for (const vid of url.videos) {\n yield ' <video:video>\\n';\n yield ` <video:thumbnail_loc>${escapeXml(vid.thumbnailLoc)}</video:thumbnail_loc>\\n`;\n yield ` <video:title>${escapeXml(vid.title)}</video:title>\\n`;\n yield ` <video:description>${escapeXml(vid.description)}</video:description>\\n`;\n if (vid.contentLoc)\n yield ` <video:content_loc>${escapeXml(vid.contentLoc)}</video:content_loc>\\n`;\n if (vid.playerLoc)\n yield ` <video:player_loc>${escapeXml(vid.playerLoc)}</video:player_loc>\\n`;\n yield ' </video:video>\\n';\n }\n }\n\n // News\n if (url.news) {\n yield ' <news:news>\\n';\n yield ' <news:publication>\\n';\n yield ` <news:name>${escapeXml(url.news.publication.name)}</news:name>\\n`;\n yield ` <news:language>${escapeXml(url.news.publication.language)}</news:language>\\n`;\n yield ' </news:publication>\\n';\n yield ` <news:publication_date>${escapeXml(url.news.publicationDate)}</news:publication_date>\\n`;\n yield ` <news:title>${escapeXml(url.news.title)}</news:title>\\n`;\n yield ' </news:news>\\n';\n }\n\n yield ' </url>\\n';\n }\n\n yield '</urlset>\\n';\n}\n","// @power-seo/sitemap — URL Validation\n// ----------------------------------------------------------------------------\n\nimport type { SitemapURL } from '@power-seo/core';\nimport { isAbsoluteUrl, MAX_URL_LENGTH } from '@power-seo/core';\nimport type { SitemapValidationResult } from './types.js';\n\nconst VALID_CHANGEFREQ = ['always', 'hourly', 'daily', 'weekly', 'monthly', 'yearly', 'never'];\n\n/**\n * Validate a sitemap URL entry against the sitemap protocol spec.\n */\nexport function validateSitemapUrl(url: SitemapURL): SitemapValidationResult {\n const errors: string[] = [];\n const warnings: string[] = [];\n\n // loc is required\n if (!url.loc || url.loc.trim().length === 0) {\n errors.push('URL \"loc\" is required and cannot be empty.');\n } else {\n if (!isAbsoluteUrl(url.loc)) {\n errors.push(`URL \"${url.loc}\" must be an absolute URL (starting with http:// or https://).`);\n }\n\n if (url.loc.length > 2048) {\n errors.push(`URL \"${url.loc}\" exceeds the maximum length of 2048 characters.`);\n }\n\n if (url.loc.length > MAX_URL_LENGTH) {\n warnings.push(\n `URL \"${url.loc}\" is ${url.loc.length} characters. URLs under ${MAX_URL_LENGTH} characters are recommended for SEO.`,\n );\n }\n }\n\n // lastmod validation\n if (url.lastmod) {\n const dateRegex = /^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}(:\\d{2})?([+-]\\d{2}:\\d{2}|Z)?)?$/;\n if (!dateRegex.test(url.lastmod)) {\n errors.push(\n `\"lastmod\" value \"${url.lastmod}\" is not a valid W3C datetime format (YYYY-MM-DD or ISO 8601).`,\n );\n }\n }\n\n // changefreq validation\n if (url.changefreq && !VALID_CHANGEFREQ.includes(url.changefreq)) {\n errors.push(\n `\"changefreq\" value \"${url.changefreq}\" is invalid. Must be one of: ${VALID_CHANGEFREQ.join(', ')}.`,\n );\n }\n\n // priority validation\n if (url.priority !== undefined) {\n if (url.priority < 0 || url.priority > 1) {\n errors.push(`\"priority\" value ${url.priority} is out of range. Must be between 0.0 and 1.0.`);\n }\n }\n\n // Image validation\n if (url.images) {\n for (let i = 0; i < url.images.length; i++) {\n const img = url.images[i]!;\n if (!img.loc || img.loc.trim().length === 0) {\n errors.push(`Image ${i + 1}: \"loc\" is required.`);\n } else if (!isAbsoluteUrl(img.loc)) {\n errors.push(`Image ${i + 1}: \"${img.loc}\" must be an absolute URL.`);\n }\n }\n if (url.images.length > 1000) {\n warnings.push(\n `URL has ${url.images.length} images. Google supports up to 1,000 images per page.`,\n );\n }\n }\n\n // Video validation\n if (url.videos) {\n for (let i = 0; i < url.videos.length; i++) {\n const vid = url.videos[i]!;\n if (!vid.title) errors.push(`Video ${i + 1}: \"title\" is required.`);\n if (!vid.description) errors.push(`Video ${i + 1}: \"description\" is required.`);\n if (!vid.thumbnailLoc) errors.push(`Video ${i + 1}: \"thumbnailLoc\" is required.`);\n if (!vid.contentLoc && !vid.playerLoc) {\n errors.push(`Video ${i + 1}: either \"contentLoc\" or \"playerLoc\" must be provided.`);\n }\n if (vid.rating !== undefined && (vid.rating < 0 || vid.rating > 5)) {\n errors.push(`Video ${i + 1}: \"rating\" must be between 0.0 and 5.0.`);\n }\n }\n }\n\n // News validation\n if (url.news) {\n if (!url.news.publication?.name) errors.push('News: \"publication.name\" is required.');\n if (!url.news.publication?.language) errors.push('News: \"publication.language\" is required.');\n if (!url.news.publicationDate) errors.push('News: \"publicationDate\" is required.');\n if (!url.news.title) errors.push('News: \"title\" is required.');\n }\n\n return {\n valid: errors.length === 0,\n errors,\n warnings,\n };\n}\n","// @power-seo/sitemap — Next.js App Router Adapter\n\n\nimport type { SitemapURL } from '@power-seo/core';\nimport { validateSitemapUrl } from './validate.js';\n\n/** Plain object matching the shape of Next.js `MetadataRoute.Sitemap[number]`. */\nexport interface NextSitemapEntry {\n url: string;\n lastModified?: string | Date;\n changeFrequency?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';\n priority?: number;\n}\n\n/**\n * Convert `SitemapURL[]` to a plain array compatible with Next.js `MetadataRoute.Sitemap`.\n *\n * @example\n * ```ts\n * // app/sitemap.ts\n * import { toNextSitemap } from '@power-seo/sitemap';\n * export default async function sitemap() {\n * return toNextSitemap(urls) as MetadataRoute.Sitemap;\n * }\n * ```\n */\nexport function toNextSitemap(urls: SitemapURL[]): NextSitemapEntry[] {\n const entries: NextSitemapEntry[] = [];\n for (const url of urls) {\n const { valid } = validateSitemapUrl(url);\n if (!valid) continue;\n const entry: NextSitemapEntry = { url: url.loc };\n if (url.lastmod) entry.lastModified = url.lastmod;\n if (url.changefreq) entry.changeFrequency = url.changefreq as NextSitemapEntry['changeFrequency'];\n if (url.priority !== undefined) entry.priority = url.priority;\n entries.push(entry);\n }\n return entries;\n}\n"],"mappings":"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","names":["escapeXml","indexEntries","normalizeUrl","escapeXml"]}
|
|
1
|
+
{"version":3,"sources":["../src/generator.ts","../src/types.ts","../src/sitemap-index.ts","../src/stream.ts","../src/validate.ts","../src/next-adapter.ts"],"sourcesContent":["// @power-seo/sitemap — XML Sitemap Generator\n// ----------------------------------------------------------------------------\n\nimport type {\n SitemapConfig,\n SitemapURL,\n SitemapImage,\n SitemapVideo,\n SitemapNews,\n} from '@power-seo/core';\nimport { normalizeUrl } from '@power-seo/core';\n\n/** Escape special XML characters. */\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\n/** Determine which namespace extensions are needed. */\nfunction detectNamespaces(urls: SitemapURL[]): { image: boolean; video: boolean; news: boolean } {\n let image = false;\n let video = false;\n let news = false;\n for (const url of urls) {\n if (url.images && url.images.length > 0) image = true;\n if (url.videos && url.videos.length > 0) video = true;\n if (url.news) news = true;\n if (image && video && news) break;\n }\n return { image, video, news };\n}\n\nfunction buildImageXml(images: SitemapImage[]): string {\n return images\n .map((img) => {\n let xml = ' <image:image>\\n';\n xml += ` <image:loc>${escapeXml(img.loc)}</image:loc>\\n`;\n if (img.caption) xml += ` <image:caption>${escapeXml(img.caption)}</image:caption>\\n`;\n if (img.geoLocation)\n xml += ` <image:geo_location>${escapeXml(img.geoLocation)}</image:geo_location>\\n`;\n if (img.title) xml += ` <image:title>${escapeXml(img.title)}</image:title>\\n`;\n if (img.license) xml += ` <image:license>${escapeXml(img.license)}</image:license>\\n`;\n xml += ' </image:image>\\n';\n return xml;\n })\n .join('');\n}\n\nfunction buildVideoXml(videos: SitemapVideo[]): string {\n return videos\n .map((vid) => {\n let xml = ' <video:video>\\n';\n xml += ` <video:thumbnail_loc>${escapeXml(vid.thumbnailLoc)}</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 if (vid.contentLoc)\n xml += ` <video:content_loc>${escapeXml(vid.contentLoc)}</video:content_loc>\\n`;\n if (vid.playerLoc)\n xml += ` <video:player_loc>${escapeXml(vid.playerLoc)}</video:player_loc>\\n`;\n if (vid.duration !== undefined)\n xml += ` <video:duration>${vid.duration}</video:duration>\\n`;\n if (vid.expirationDate)\n xml += ` <video:expiration_date>${escapeXml(vid.expirationDate)}</video:expiration_date>\\n`;\n if (vid.rating !== undefined) xml += ` <video:rating>${vid.rating}</video:rating>\\n`;\n if (vid.viewCount !== undefined)\n xml += ` <video:view_count>${vid.viewCount}</video:view_count>\\n`;\n if (vid.publicationDate)\n xml += ` <video:publication_date>${escapeXml(vid.publicationDate)}</video:publication_date>\\n`;\n if (vid.familyFriendly !== undefined)\n xml += ` <video:family_friendly>${vid.familyFriendly ? 'yes' : 'no'}</video:family_friendly>\\n`;\n if (vid.live !== undefined)\n xml += ` <video:live>${vid.live ? 'yes' : 'no'}</video:live>\\n`;\n xml += ' </video:video>\\n';\n return xml;\n })\n .join('');\n}\n\nfunction buildNewsXml(news: SitemapNews): string {\n let xml = ' <news:news>\\n';\n xml += ' <news:publication>\\n';\n xml += ` <news:name>${escapeXml(news.publication.name)}</news:name>\\n`;\n xml += ` <news:language>${escapeXml(news.publication.language)}</news:language>\\n`;\n xml += ' </news:publication>\\n';\n xml += ` <news:publication_date>${escapeXml(news.publicationDate)}</news:publication_date>\\n`;\n xml += ` <news:title>${escapeXml(news.title)}</news:title>\\n`;\n xml += ' </news:news>\\n';\n return xml;\n}\n\nfunction buildUrlXml(url: SitemapURL, hostname: string): string {\n const loc = url.loc.startsWith('http')\n ? url.loc\n : normalizeUrl(`${hostname}${url.loc.startsWith('/') ? '' : '/'}${url.loc}`);\n\n let xml = ' <url>\\n';\n xml += ` <loc>${escapeXml(loc)}</loc>\\n`;\n if (url.lastmod) xml += ` <lastmod>${escapeXml(url.lastmod)}</lastmod>\\n`;\n if (url.changefreq) xml += ` <changefreq>${url.changefreq}</changefreq>\\n`;\n if (url.priority !== undefined) xml += ` <priority>${url.priority.toFixed(1)}</priority>\\n`;\n if (url.images && url.images.length > 0) xml += buildImageXml(url.images);\n if (url.videos && url.videos.length > 0) xml += buildVideoXml(url.videos);\n if (url.news) xml += buildNewsXml(url.news);\n xml += ' </url>\\n';\n return xml;\n}\n\n/**\n * Generate an XML sitemap string from a sitemap configuration.\n *\n * @example\n * ```ts\n * const xml = generateSitemap({\n * hostname: 'https://example.com',\n * urls: [\n * { loc: '/', changefreq: 'daily', priority: 1.0 },\n * { loc: '/about', changefreq: 'monthly', priority: 0.8 },\n * ],\n * });\n * ```\n */\nexport function generateSitemap(config: SitemapConfig): string {\n const { hostname, urls } = config;\n const ns = detectNamespaces(urls);\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 if (ns.image) xml += '\\n xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\"';\n if (ns.video) xml += '\\n xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\"';\n if (ns.news) xml += '\\n xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\"';\n xml += '>\\n';\n\n for (const url of urls) {\n xml += buildUrlXml(url, hostname);\n }\n\n xml += '</urlset>\\n';\n return xml;\n}\n","// @power-seo/sitemap — Types\n// ----------------------------------------------------------------------------\n\nexport type {\n SitemapURL,\n SitemapImage,\n SitemapVideo,\n SitemapNews,\n SitemapConfig,\n} from '@power-seo/core';\n\n/** A sitemap entry in a sitemap index. */\nexport interface SitemapIndexEntry {\n loc: string;\n lastmod?: string;\n}\n\n/** Configuration for the sitemap index generator. */\nexport interface SitemapIndexConfig {\n sitemaps: SitemapIndexEntry[];\n}\n\n/** Result of URL validation. */\nexport interface SitemapValidationResult {\n valid: boolean;\n errors: string[];\n warnings: string[];\n}\n\n/** Maximum URLs allowed per sitemap file. */\nexport const MAX_URLS_PER_SITEMAP = 50_000 as const;\n\n/** Maximum sitemap file size in bytes (50MB). */\nexport const MAX_SITEMAP_SIZE_BYTES = 52_428_800 as const;\n","// @power-seo/sitemap — Sitemap Index Generator\n// ----------------------------------------------------------------------------\n\nimport type { SitemapConfig } from '@power-seo/core';\nimport type { SitemapIndexConfig, SitemapIndexEntry } from './types.js';\nimport { MAX_URLS_PER_SITEMAP } from './types.js';\nimport { generateSitemap } from './generator.js';\n\n/** Escape special XML characters. */\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\n/**\n * Generate a sitemap index XML string.\n *\n * @example\n * ```ts\n * const indexXml = generateSitemapIndex({\n * sitemaps: [\n * { loc: 'https://example.com/sitemap-0.xml', lastmod: '2024-01-01' },\n * { loc: 'https://example.com/sitemap-1.xml', lastmod: '2024-01-01' },\n * ],\n * });\n * ```\n */\nexport function generateSitemapIndex(config: SitemapIndexConfig): string {\n let xml = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n';\n xml += '<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\\n';\n\n for (const sitemap of config.sitemaps) {\n xml += ' <sitemap>\\n';\n xml += ` <loc>${escapeXml(sitemap.loc)}</loc>\\n`;\n if (sitemap.lastmod) {\n xml += ` <lastmod>${escapeXml(sitemap.lastmod)}</lastmod>\\n`;\n }\n xml += ' </sitemap>\\n';\n }\n\n xml += '</sitemapindex>\\n';\n return xml;\n}\n\n/**\n * Split a large sitemap config into multiple sitemaps + an index.\n *\n * When a site has more than 50,000 URLs, this function splits them into\n * multiple sitemap files and returns both the individual sitemaps and\n * the index that references them.\n *\n * @example\n * ```ts\n * const { index, sitemaps } = splitSitemap({\n * hostname: 'https://example.com',\n * urls: largeUrlArray,\n * });\n * // sitemaps[0].xml, sitemaps[1].xml, etc.\n * // index = sitemap index XML\n * ```\n */\nexport function splitSitemap(\n config: SitemapConfig,\n sitemapUrlPattern = '/sitemap-{index}.xml',\n): { index: string; sitemaps: Array<{ filename: string; xml: string }> } {\n const maxPerSitemap = config.maxUrlsPerSitemap ?? MAX_URLS_PER_SITEMAP;\n const { hostname, urls } = config;\n\n if (urls.length <= maxPerSitemap) {\n const xml = generateSitemap(config);\n const filename = sitemapUrlPattern.replace('{index}', '0');\n const indexEntries: SitemapIndexEntry[] = [{ loc: `${hostname}${filename}` }];\n return {\n index: generateSitemapIndex({ sitemaps: indexEntries }),\n sitemaps: [{ filename, xml }],\n };\n }\n\n const sitemaps: Array<{ filename: string; xml: string }> = [];\n const indexEntries: SitemapIndexEntry[] = [];\n\n for (let i = 0; i < urls.length; i += maxPerSitemap) {\n const chunk = urls.slice(i, i + maxPerSitemap);\n const chunkIndex = Math.floor(i / maxPerSitemap);\n const filename = sitemapUrlPattern.replace('{index}', String(chunkIndex));\n const xml = generateSitemap({ hostname, urls: chunk });\n\n sitemaps.push({ filename, xml });\n indexEntries.push({ loc: `${hostname}${filename}` });\n }\n\n return {\n index: generateSitemapIndex({ sitemaps: indexEntries }),\n sitemaps,\n };\n}\n","// @power-seo/sitemap — Streaming Sitemap Generator\n// ----------------------------------------------------------------------------\n\nimport type { SitemapURL } from '@power-seo/core';\nimport { normalizeUrl } from '@power-seo/core';\n\n/** Escape special XML characters. */\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\n/**\n * A streaming sitemap generator that yields XML chunks.\n *\n * Useful for server responses where you want to stream the sitemap\n * instead of building the entire XML string in memory.\n *\n * @example\n * ```ts\n * const stream = streamSitemap('https://example.com', urls);\n * for (const chunk of stream) {\n * response.write(chunk);\n * }\n * ```\n */\nexport function* streamSitemap(\n hostname: string,\n urls: Iterable<SitemapURL>,\n): Generator<string, void, undefined> {\n yield '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n';\n yield '<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\"\\n';\n yield ' xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\"\\n';\n yield ' xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\"\\n';\n yield ' xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\">\\n';\n\n for (const url of urls) {\n const loc = url.loc.startsWith('http')\n ? url.loc\n : normalizeUrl(`${hostname}${url.loc.startsWith('/') ? '' : '/'}${url.loc}`);\n\n yield ' <url>\\n';\n yield ` <loc>${escapeXml(loc)}</loc>\\n`;\n\n if (url.lastmod) yield ` <lastmod>${escapeXml(url.lastmod)}</lastmod>\\n`;\n if (url.changefreq) yield ` <changefreq>${url.changefreq}</changefreq>\\n`;\n if (url.priority !== undefined) yield ` <priority>${url.priority.toFixed(1)}</priority>\\n`;\n\n // Images\n if (url.images) {\n for (const img of url.images) {\n yield ' <image:image>\\n';\n yield ` <image:loc>${escapeXml(img.loc)}</image:loc>\\n`;\n if (img.caption) yield ` <image:caption>${escapeXml(img.caption)}</image:caption>\\n`;\n if (img.title) yield ` <image:title>${escapeXml(img.title)}</image:title>\\n`;\n yield ' </image:image>\\n';\n }\n }\n\n // Videos\n if (url.videos) {\n for (const vid of url.videos) {\n yield ' <video:video>\\n';\n yield ` <video:thumbnail_loc>${escapeXml(vid.thumbnailLoc)}</video:thumbnail_loc>\\n`;\n yield ` <video:title>${escapeXml(vid.title)}</video:title>\\n`;\n yield ` <video:description>${escapeXml(vid.description)}</video:description>\\n`;\n if (vid.contentLoc)\n yield ` <video:content_loc>${escapeXml(vid.contentLoc)}</video:content_loc>\\n`;\n if (vid.playerLoc)\n yield ` <video:player_loc>${escapeXml(vid.playerLoc)}</video:player_loc>\\n`;\n yield ' </video:video>\\n';\n }\n }\n\n // News\n if (url.news) {\n yield ' <news:news>\\n';\n yield ' <news:publication>\\n';\n yield ` <news:name>${escapeXml(url.news.publication.name)}</news:name>\\n`;\n yield ` <news:language>${escapeXml(url.news.publication.language)}</news:language>\\n`;\n yield ' </news:publication>\\n';\n yield ` <news:publication_date>${escapeXml(url.news.publicationDate)}</news:publication_date>\\n`;\n yield ` <news:title>${escapeXml(url.news.title)}</news:title>\\n`;\n yield ' </news:news>\\n';\n }\n\n yield ' </url>\\n';\n }\n\n yield '</urlset>\\n';\n}\n","// @power-seo/sitemap — URL Validation\n// ----------------------------------------------------------------------------\n\nimport type { SitemapURL } from '@power-seo/core';\nimport { isAbsoluteUrl, MAX_URL_LENGTH } from '@power-seo/core';\nimport type { SitemapValidationResult } from './types.js';\n\nconst VALID_CHANGEFREQ = ['always', 'hourly', 'daily', 'weekly', 'monthly', 'yearly', 'never'];\n\n/**\n * Validate a sitemap URL entry against the sitemap protocol spec.\n */\nexport function validateSitemapUrl(url: SitemapURL): SitemapValidationResult {\n const errors: string[] = [];\n const warnings: string[] = [];\n\n // loc is required\n if (!url.loc || url.loc.trim().length === 0) {\n errors.push('URL \"loc\" is required and cannot be empty.');\n } else {\n if (!isAbsoluteUrl(url.loc)) {\n errors.push(`URL \"${url.loc}\" must be an absolute URL (starting with http:// or https://).`);\n }\n\n if (url.loc.length > 2048) {\n errors.push(`URL \"${url.loc}\" exceeds the maximum length of 2048 characters.`);\n }\n\n if (url.loc.length > MAX_URL_LENGTH) {\n warnings.push(\n `URL \"${url.loc}\" is ${url.loc.length} characters. URLs under ${MAX_URL_LENGTH} characters are recommended for SEO.`,\n );\n }\n }\n\n // lastmod validation\n if (url.lastmod) {\n const dateRegex = /^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}(:\\d{2}(\\.\\d{1,3})?)?([+-]\\d{2}:\\d{2}|Z)?)?$/;\n if (!dateRegex.test(url.lastmod)) {\n errors.push(\n `\"lastmod\" value \"${url.lastmod}\" is not a valid W3C datetime format (YYYY-MM-DD or ISO 8601).`,\n );\n }\n }\n\n // changefreq validation\n if (url.changefreq && !VALID_CHANGEFREQ.includes(url.changefreq)) {\n errors.push(\n `\"changefreq\" value \"${url.changefreq}\" is invalid. Must be one of: ${VALID_CHANGEFREQ.join(', ')}.`,\n );\n }\n\n // priority validation\n if (url.priority !== undefined) {\n if (url.priority < 0 || url.priority > 1) {\n errors.push(`\"priority\" value ${url.priority} is out of range. Must be between 0.0 and 1.0.`);\n }\n }\n\n // Image validation\n if (url.images) {\n for (let i = 0; i < url.images.length; i++) {\n const img = url.images[i]!;\n if (!img.loc || img.loc.trim().length === 0) {\n errors.push(`Image ${i + 1}: \"loc\" is required.`);\n } else if (!isAbsoluteUrl(img.loc)) {\n errors.push(`Image ${i + 1}: \"${img.loc}\" must be an absolute URL.`);\n }\n }\n if (url.images.length > 1000) {\n warnings.push(\n `URL has ${url.images.length} images. Google supports up to 1,000 images per page.`,\n );\n }\n }\n\n // Video validation\n if (url.videos) {\n for (let i = 0; i < url.videos.length; i++) {\n const vid = url.videos[i]!;\n if (!vid.title) errors.push(`Video ${i + 1}: \"title\" is required.`);\n if (!vid.description) errors.push(`Video ${i + 1}: \"description\" is required.`);\n if (!vid.thumbnailLoc) errors.push(`Video ${i + 1}: \"thumbnailLoc\" is required.`);\n if (!vid.contentLoc && !vid.playerLoc) {\n errors.push(`Video ${i + 1}: either \"contentLoc\" or \"playerLoc\" must be provided.`);\n }\n if (vid.rating !== undefined && (vid.rating < 0 || vid.rating > 5)) {\n errors.push(`Video ${i + 1}: \"rating\" must be between 0.0 and 5.0.`);\n }\n }\n }\n\n // News validation\n if (url.news) {\n if (!url.news.publication?.name) errors.push('News: \"publication.name\" is required.');\n if (!url.news.publication?.language) errors.push('News: \"publication.language\" is required.');\n if (!url.news.publicationDate) errors.push('News: \"publicationDate\" is required.');\n if (!url.news.title) errors.push('News: \"title\" is required.');\n }\n\n return {\n valid: errors.length === 0,\n errors,\n warnings,\n };\n}\n","// @power-seo/sitemap — Next.js App Router Adapter\n\n\nimport type { SitemapURL } from '@power-seo/core';\nimport { validateSitemapUrl } from './validate.js';\n\n/** Plain object matching the shape of Next.js `MetadataRoute.Sitemap[number]`. */\nexport interface NextSitemapEntry {\n url: string;\n lastModified?: string | Date;\n changeFrequency?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';\n priority?: number;\n}\n\n/**\n * Convert `SitemapURL[]` to a plain array compatible with Next.js `MetadataRoute.Sitemap`.\n *\n * @example\n * ```ts\n * // app/sitemap.ts\n * import { toNextSitemap } from '@power-seo/sitemap';\n * export default async function sitemap() {\n * return toNextSitemap(urls) as MetadataRoute.Sitemap;\n * }\n * ```\n */\nexport function toNextSitemap(urls: SitemapURL[]): NextSitemapEntry[] {\n const entries: NextSitemapEntry[] = [];\n for (const url of urls) {\n const { valid } = validateSitemapUrl(url);\n if (!valid) continue;\n const entry: NextSitemapEntry = { url: url.loc };\n if (url.lastmod) entry.lastModified = url.lastmod;\n if (url.changefreq) entry.changeFrequency = url.changefreq as NextSitemapEntry['changeFrequency'];\n if (url.priority !== undefined) entry.priority = url.priority;\n entries.push(entry);\n }\n return entries;\n}\n"],"mappings":"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","names":["escapeXml","indexEntries","normalizeUrl","escapeXml"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@power-seo/sitemap",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.15",
|
|
4
4
|
"description": "XML sitemap generation, streaming, and validation with image, video, and news support",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"seo",
|
|
@@ -37,10 +37,16 @@
|
|
|
37
37
|
"module": "./dist/index.js",
|
|
38
38
|
"types": "./dist/index.d.ts",
|
|
39
39
|
"files": [
|
|
40
|
-
"dist"
|
|
40
|
+
"dist",
|
|
41
|
+
"LICENSE"
|
|
41
42
|
],
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=18.0.0",
|
|
45
|
+
"npm": ">=9.0.0",
|
|
46
|
+
"pnpm": ">=8.0.0"
|
|
47
|
+
},
|
|
42
48
|
"dependencies": {
|
|
43
|
-
"@power-seo/core": "1.0.
|
|
49
|
+
"@power-seo/core": "1.0.15"
|
|
44
50
|
},
|
|
45
51
|
"devDependencies": {
|
|
46
52
|
"rimraf": "^6.1.3",
|