@xexr/blog-platform 1.1.5 → 1.2.0

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.
Files changed (37) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +21 -0
  3. package/dist/app/api/blog/publish/route.test.ts +55 -0
  4. package/dist/app/api/blog/publish/route.ts +7 -2
  5. package/dist/app/blog/[slug]/page.tsx +41 -28
  6. package/dist/components/ArchiveStructuredData.tsx +61 -0
  7. package/dist/components/ArticleVideo.tsx +136 -0
  8. package/dist/components/SeriesArchivePages.tsx +10 -0
  9. package/dist/components/TopicArchivePages.test.ts +52 -4
  10. package/dist/components/TopicArchivePages.tsx +14 -4
  11. package/dist/{index-CKWrb9a4.d.mts → index-CmjKzrH-.d.mts} +30 -1
  12. package/dist/{index-CKWrb9a4.d.ts → index-CmjKzrH-.d.ts} +30 -1
  13. package/dist/index.d.mts +52 -5
  14. package/dist/index.d.ts +52 -5
  15. package/dist/index.js +14 -14
  16. package/dist/index.js.map +1 -1
  17. package/dist/index.mjs +14 -14
  18. package/dist/index.mjs.map +1 -1
  19. package/dist/lib/related-articles.d.mts +3 -2
  20. package/dist/lib/related-articles.d.ts +3 -2
  21. package/dist/lib/sitemap.ts +2 -42
  22. package/dist/lib/storage/drizzle.ts +9 -0
  23. package/dist/lib/structured-data.ts +62 -11
  24. package/dist/lib/video.ts +163 -0
  25. package/dist/{sitemap-BDT3d496.d.ts → sitemap-CEjqxRaQ.d.ts} +5 -4
  26. package/dist/{sitemap-D3rS0iAF.d.mts → sitemap-D1eInRA1.d.mts} +5 -4
  27. package/dist/sitemap.d.mts +2 -2
  28. package/dist/sitemap.d.ts +2 -2
  29. package/dist/sitemap.js +4 -4
  30. package/dist/sitemap.js.map +1 -1
  31. package/dist/sitemap.mjs +4 -4
  32. package/dist/sitemap.mjs.map +1 -1
  33. package/dist/types/contracts.ts +38 -0
  34. package/dist/types/index.d.mts +1 -1
  35. package/dist/types/index.d.ts +1 -1
  36. package/dist/types/index.ts +1 -0
  37. package/package.json +17 -13
@@ -1,4 +1,4 @@
1
- import { P as PublishedTopic, a as PublishedArticle } from '../index-CKWrb9a4.mjs';
1
+ import { PublishedTopic, PublishedArticle } from '../index.mjs';
2
2
 
3
3
  interface RelatedArticleCandidate {
4
4
  readonly id: string;
@@ -14,4 +14,5 @@ interface RelatedFeaturedArticlesOptions {
14
14
  declare function getRelatedFeaturedArticles<TArticle extends RelatedArticleCandidate>(currentArticle: RelatedArticleCandidate, articles: readonly TArticle[], options?: RelatedFeaturedArticlesOptions): TArticle[];
15
15
  type RelatedPublishedArticle = PublishedArticle;
16
16
 
17
- export { type RelatedArticleCandidate, type RelatedFeaturedArticlesOptions, type RelatedPublishedArticle, getRelatedFeaturedArticles };
17
+ export { getRelatedFeaturedArticles };
18
+ export type { RelatedArticleCandidate, RelatedFeaturedArticlesOptions, RelatedPublishedArticle };
@@ -1,4 +1,4 @@
1
- import { P as PublishedTopic, a as PublishedArticle } from '../index-CKWrb9a4.js';
1
+ import { PublishedTopic, PublishedArticle } from '../index.js';
2
2
 
3
3
  interface RelatedArticleCandidate {
4
4
  readonly id: string;
@@ -14,4 +14,5 @@ interface RelatedFeaturedArticlesOptions {
14
14
  declare function getRelatedFeaturedArticles<TArticle extends RelatedArticleCandidate>(currentArticle: RelatedArticleCandidate, articles: readonly TArticle[], options?: RelatedFeaturedArticlesOptions): TArticle[];
15
15
  type RelatedPublishedArticle = PublishedArticle;
16
16
 
17
- export { type RelatedArticleCandidate, type RelatedFeaturedArticlesOptions, type RelatedPublishedArticle, getRelatedFeaturedArticles };
17
+ export { getRelatedFeaturedArticles };
18
+ export type { RelatedArticleCandidate, RelatedFeaturedArticlesOptions, RelatedPublishedArticle };
@@ -56,10 +56,10 @@ export interface SitemapOptions {
56
56
  /** Whether to include series pages in sitemap */
57
57
  includeSeriesPages?: boolean;
58
58
 
59
- /** Whether to include pagination pages in sitemap */
59
+ /** @deprecated Pagination pages are never included. */
60
60
  includePaginationPages?: boolean;
61
61
 
62
- /** Number of articles per page for pagination calculation */
62
+ /** @deprecated Pagination pages are never included. */
63
63
  articlesPerPage?: number;
64
64
 
65
65
  /** Maximum number of sitemap entries (defaults to 50,000 per Google guidelines) */
@@ -213,8 +213,6 @@ export async function generateBlogSitemapEntries(
213
213
  includeAuthorPages = true,
214
214
  includeTopicPages = true,
215
215
  includeSeriesPages = true,
216
- includePaginationPages = true,
217
- articlesPerPage = 10,
218
216
  maxEntries = 50000,
219
217
  changeFrequency = {},
220
218
  priority = {},
@@ -338,22 +336,6 @@ export async function generateBlogSitemapEntries(
338
336
  });
339
337
  }
340
338
 
341
- // Add pagination pages if enabled
342
- if (includePaginationPages && articles.length > articlesPerPage) {
343
- const totalPages = Math.ceil(articles.length / articlesPerPage);
344
-
345
- for (let page = 2; page <= totalPages; page++) {
346
- if (entries.length >= maxEntries) break;
347
-
348
- entries.push({
349
- url: `${baseUrl}${pathPrefix}?page=${page}`,
350
- lastModified: getLatestModificationDate(articles),
351
- changeFrequency: changeFrequency.paginationPages ?? "daily",
352
- priority: priority.paginationPages ?? 0.5,
353
- });
354
- }
355
- }
356
-
357
339
  return entries;
358
340
  }
359
341
 
@@ -377,8 +359,6 @@ export function generateSitemap(
377
359
  includeAuthorPages = true,
378
360
  includeTopicPages = true,
379
361
  includeSeriesPages = true,
380
- includePaginationPages = true,
381
- articlesPerPage = 10,
382
362
  maxEntries = 50000,
383
363
  changeFrequency = {},
384
364
  priority = {},
@@ -410,10 +390,6 @@ export function generateSitemap(
410
390
  potentialEntries +=
411
391
  sitemapAuthors.length > 0 ? sitemapAuthors.length + 1 : 0; // Authors index + author pages
412
392
  }
413
- if (includePaginationPages && articles.length > articlesPerPage) {
414
- const totalPages = Math.ceil(articles.length / articlesPerPage);
415
- potentialEntries += totalPages - 1; // Pagination pages (excluding page 1 which is home)
416
- }
417
393
 
418
394
  const willTruncate = potentialEntries > maxEntries;
419
395
 
@@ -523,22 +499,6 @@ export function generateSitemap(
523
499
  });
524
500
  }
525
501
 
526
- // Add pagination pages if enabled
527
- if (includePaginationPages && articles.length > articlesPerPage) {
528
- const totalPages = Math.ceil(articles.length / articlesPerPage);
529
-
530
- for (let page = 2; page <= totalPages; page++) {
531
- if (entries.length >= maxEntries) break;
532
-
533
- entries.push({
534
- loc: `${baseUrl}${pathPrefix}?page=${page}`,
535
- lastmod: getLatestModificationDate(articles),
536
- changefreq: changeFrequency.paginationPages ?? "daily",
537
- priority: priority.paginationPages ?? 0.5,
538
- });
539
- }
540
- }
541
-
542
502
  return {
543
503
  entries,
544
504
  totalEntries: entries.length,
@@ -100,6 +100,7 @@ function toRow(article: PublishedArticle): ArticleRow {
100
100
  __bloggyTopics: article.topics ?? [],
101
101
  __bloggySeries: article.series ?? null,
102
102
  __bloggyCta: article.cta ?? null,
103
+ ...(article.video && { __bloggyVideo: article.video }),
103
104
  }),
104
105
  version: article.version,
105
106
  };
@@ -128,6 +129,7 @@ function fromRow(row: ArticleRow): PublishedArticle {
128
129
  cta: extractCta(row.metaJson),
129
130
  topics: extractTopics(row.metaJson),
130
131
  series: extractSeries(row.metaJson),
132
+ ...withVideo(row.metaJson),
131
133
  meta: extractMeta(row.metaJson),
132
134
  version: row.version,
133
135
  };
@@ -137,6 +139,7 @@ type StoredArticleMeta = PublishedArticle["meta"] & {
137
139
  __bloggyTopics?: PublishedArticle["topics"];
138
140
  __bloggySeries?: PublishedArticle["series"] | null;
139
141
  __bloggyCta?: PublishedArticle["cta"] | null;
142
+ __bloggyVideo?: PublishedArticle["video"];
140
143
  };
141
144
 
142
145
  function parseStoredMeta(metaJson: string): StoredArticleMeta {
@@ -158,8 +161,14 @@ function extractSeries(metaJson: string): PublishedArticle["series"] {
158
161
  return parsed.__bloggySeries ?? undefined;
159
162
  }
160
163
 
164
+ function withVideo(metaJson: string): Pick<PublishedArticle, "video"> {
165
+ const video = parseStoredMeta(metaJson).__bloggyVideo;
166
+ return video ? { video } : {};
167
+ }
168
+
161
169
  function extractMeta(metaJson: string): PublishedArticle["meta"] {
162
170
  const parsed = parseStoredMeta(metaJson);
171
+ delete parsed.__bloggyVideo;
163
172
  delete parsed.__bloggyTopics;
164
173
  delete parsed.__bloggySeries;
165
174
  delete parsed.__bloggyCta;
@@ -15,6 +15,11 @@ import type {
15
15
  PublishedSite,
16
16
  } from "../types/contracts";
17
17
  import { buildAuthorPath } from "./urls";
18
+ import {
19
+ buildYouTubeStructuredDataEmbedUrl,
20
+ buildYouTubeThumbnailUrl,
21
+ isYouTubeVideoId,
22
+ } from "./video";
18
23
 
19
24
  /**
20
25
  * Base schema.org context and type
@@ -95,6 +100,21 @@ export interface BlogPostingSchema extends BaseStructuredData {
95
100
  "@type": "Thing";
96
101
  name: string;
97
102
  }[];
103
+ video?: VideoObjectSchema;
104
+ }
105
+
106
+ /**
107
+ * VideoObject schema for an article's embedded video
108
+ * @see https://schema.org/VideoObject
109
+ */
110
+ export interface VideoObjectSchema {
111
+ "@type": "VideoObject";
112
+ name: string;
113
+ description: string;
114
+ thumbnailUrl: string[];
115
+ uploadDate: string;
116
+ duration?: string;
117
+ embedUrl: string;
98
118
  }
99
119
 
100
120
  /**
@@ -205,6 +225,11 @@ export function generateArticleStructuredData(
205
225
  isAccessibleForFree,
206
226
  };
207
227
 
228
+ const videoSchema = generateVideoStructuredData(article);
229
+ if (videoSchema) {
230
+ schema.video = videoSchema;
231
+ }
232
+
208
233
  // Add optional properties if enabled
209
234
  if (includeOptionalProperties) {
210
235
  // Featured image
@@ -222,23 +247,50 @@ export function generateArticleStructuredData(
222
247
  schema.inLanguage = language ?? article.site.locale;
223
248
  }
224
249
 
225
- // Extract topics from tone adjectives or keywords for 'about' property
226
- if (article.site.toneAdjectives.length > 0) {
227
- schema.about = article.site.toneAdjectives.map((topic) => ({
250
+ const subjects = [
251
+ ...(article.topics ?? []).map((topic) => topic.name),
252
+ ...(article.series ? [article.series.title] : []),
253
+ ];
254
+ if (subjects.length) {
255
+ schema.about = [...new Set(subjects)].map((name) => ({
228
256
  "@type": "Thing" as const,
229
- name: topic,
257
+ name,
230
258
  }));
231
259
  }
232
260
 
233
- // Genre based on site description or tone
234
- if (article.site.toneAdjectives.length > 0) {
235
- schema.genre = [...article.site.toneAdjectives];
236
- }
261
+
237
262
  }
238
263
 
239
264
  return schema;
240
265
  }
241
266
 
267
+ /**
268
+ * Generate the VideoObject for an article's video, if it has a valid one
269
+ */
270
+ export function generateVideoStructuredData(
271
+ article: PublishedArticle,
272
+ ): VideoObjectSchema | undefined {
273
+ const video = article.video;
274
+ if (!video || !isYouTubeVideoId(video.id)) {
275
+ return undefined;
276
+ }
277
+
278
+ const description = video.description?.trim() ?? "";
279
+
280
+ return {
281
+ "@type": "VideoObject",
282
+ name: video.title,
283
+ description: description.length > 0 ? description : article.excerpt,
284
+ thumbnailUrl: [
285
+ buildYouTubeThumbnailUrl(video.id),
286
+ ...(article.featuredImage ? [article.featuredImage] : []),
287
+ ],
288
+ uploadDate: video.uploadDate,
289
+ ...(video.duration && { duration: video.duration }),
290
+ embedUrl: buildYouTubeStructuredDataEmbedUrl(video.id),
291
+ };
292
+ }
293
+
242
294
  /**
243
295
  * Generate structured data for an author
244
296
  */
@@ -288,9 +340,8 @@ export function generateAuthorStructuredData(
288
340
  schema.additionalType = "GuestAuthor";
289
341
  }
290
342
 
291
- // Add knowledge areas from tone adjectives
292
- if (author.toneAdjectives && author.toneAdjectives.length > 0) {
293
- schema.knowsAbout = [...author.toneAdjectives];
343
+ if (author.expertise?.length) {
344
+ schema.knowsAbout = [...author.expertise];
294
345
  }
295
346
 
296
347
  return schema;
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Helpers for article videos (YouTube only).
3
+ *
4
+ * Every URL built here goes through `isYouTubeVideoId` first, so a stored id
5
+ * can never inject markup or a different host into an embed.
6
+ */
7
+
8
+ const YOUTUBE_VIDEO_ID_PATTERN = /^[A-Za-z0-9_-]{11}$/;
9
+ const ISO_DURATION_PATTERN = /^PT(?=\d)(?:\d+H)?(?:\d+M)?(?:\d+(?:\.\d+)?S)?$/;
10
+ const ISO_DATE_PREFIX_PATTERN = /^\d{4}-\d{2}-\d{2}(?:$|T)/;
11
+ const YOUTUBE_HOSTS = new Set([
12
+ "youtube.com",
13
+ "www.youtube.com",
14
+ "m.youtube.com",
15
+ "music.youtube.com",
16
+ "youtube-nocookie.com",
17
+ "www.youtube-nocookie.com",
18
+ "youtu.be",
19
+ ]);
20
+ const YOUTUBE_PATH_PREFIXES = ["embed", "shorts", "live", "v"];
21
+
22
+ export const ARTICLE_VIDEO_TITLE_MAX_LENGTH = 200;
23
+ export const ARTICLE_VIDEO_DESCRIPTION_MAX_LENGTH = 5000;
24
+
25
+ export function isYouTubeVideoId(value: unknown): value is string {
26
+ return typeof value === "string" && YOUTUBE_VIDEO_ID_PATTERN.test(value);
27
+ }
28
+
29
+ export function isIsoDuration(value: unknown): value is string {
30
+ return (
31
+ typeof value === "string" &&
32
+ value !== "PT" &&
33
+ ISO_DURATION_PATTERN.test(value)
34
+ );
35
+ }
36
+
37
+ /** ISO 8601 date or date-time whose calendar date exists (no 31 February). */
38
+ function isIsoDate(value: unknown): value is string {
39
+ if (typeof value !== "string" || !ISO_DATE_PREFIX_PATTERN.test(value)) {
40
+ return false;
41
+ }
42
+ const datePart = value.slice(0, 10);
43
+ const parsed = new Date(`${datePart}T00:00:00Z`);
44
+ return (
45
+ !Number.isNaN(parsed.getTime()) &&
46
+ parsed.toISOString().slice(0, 10) === datePart &&
47
+ !Number.isNaN(Date.parse(value))
48
+ );
49
+ }
50
+
51
+ /**
52
+ * Extract a YouTube video id from a bare id or a YouTube URL
53
+ * (watch, youtu.be, embed, nocookie embed, shorts, live).
54
+ */
55
+ export function parseYouTubeVideoId(input: string): string | null {
56
+ const trimmed = input.trim();
57
+ if (YOUTUBE_VIDEO_ID_PATTERN.test(trimmed)) return trimmed;
58
+ if (!trimmed) return null;
59
+
60
+ let url: URL;
61
+ try {
62
+ url = new URL(
63
+ /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`,
64
+ );
65
+ } catch {
66
+ return null;
67
+ }
68
+
69
+ const host = url.hostname.toLowerCase();
70
+ if (!YOUTUBE_HOSTS.has(host)) return null;
71
+
72
+ const segments = url.pathname.split("/").filter(Boolean);
73
+ let candidate: string | null | undefined;
74
+ if (host === "youtu.be") {
75
+ candidate = segments[0];
76
+ } else if (segments[0] === "watch") {
77
+ candidate = url.searchParams.get("v");
78
+ } else if (segments[0] && YOUTUBE_PATH_PREFIXES.includes(segments[0])) {
79
+ candidate = segments[1];
80
+ }
81
+
82
+ return isYouTubeVideoId(candidate) ? candidate : null;
83
+ }
84
+
85
+ /**
86
+ * Validate an untrusted `video` value from a publish payload.
87
+ * Returns a list of human-readable errors; empty when valid.
88
+ */
89
+ export function validatePublishedArticleVideo(value: unknown): string[] {
90
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
91
+ return ["Article video must be an object"];
92
+ }
93
+
94
+ const video = value as Record<string, unknown>;
95
+ const errors: string[] = [];
96
+
97
+ if (video.provider !== "youtube") {
98
+ errors.push('Article video provider must be "youtube"');
99
+ }
100
+ if (!isYouTubeVideoId(video.id)) {
101
+ errors.push("Article video id must be an 11-character YouTube id");
102
+ }
103
+ if (typeof video.title !== "string" || !video.title.trim()) {
104
+ errors.push("Article video title is required");
105
+ } else if (video.title.length > ARTICLE_VIDEO_TITLE_MAX_LENGTH) {
106
+ errors.push(
107
+ `Article video title must be ${ARTICLE_VIDEO_TITLE_MAX_LENGTH} characters or fewer`,
108
+ );
109
+ }
110
+ if (!isIsoDate(video.uploadDate)) {
111
+ errors.push("Article video uploadDate must be an ISO 8601 date");
112
+ }
113
+ if (video.duration !== undefined && !isIsoDuration(video.duration)) {
114
+ errors.push(
115
+ "Article video duration must be an ISO 8601 duration such as PT4M13S",
116
+ );
117
+ }
118
+ if (video.verticalId !== undefined && !isYouTubeVideoId(video.verticalId)) {
119
+ errors.push("Article video verticalId must be an 11-character YouTube id");
120
+ }
121
+ if (video.description !== undefined) {
122
+ if (typeof video.description !== "string") {
123
+ errors.push("Article video description must be a string");
124
+ } else if (
125
+ video.description.length > ARTICLE_VIDEO_DESCRIPTION_MAX_LENGTH
126
+ ) {
127
+ errors.push(
128
+ `Article video description must be ${ARTICLE_VIDEO_DESCRIPTION_MAX_LENGTH} characters or fewer`,
129
+ );
130
+ }
131
+ }
132
+
133
+ return errors;
134
+ }
135
+
136
+ function assertYouTubeVideoId(id: string): void {
137
+ if (!isYouTubeVideoId(id)) {
138
+ throw new Error("Invalid YouTube video id");
139
+ }
140
+ }
141
+
142
+ /** Privacy-enhanced player URL, loaded only after the reader presses play. */
143
+ export function buildYouTubeEmbedUrl(id: string): string {
144
+ assertYouTubeVideoId(id);
145
+ return `https://www.youtube-nocookie.com/embed/${id}?rel=0&autoplay=1`;
146
+ }
147
+
148
+ /** Canonical player URL for structured data. */
149
+ export function buildYouTubeStructuredDataEmbedUrl(id: string): string {
150
+ assertYouTubeVideoId(id);
151
+ return `https://www.youtube.com/embed/${id}`;
152
+ }
153
+
154
+ export function buildYouTubeWatchUrl(id: string): string {
155
+ assertYouTubeVideoId(id);
156
+ return `https://www.youtube.com/watch?v=${id}`;
157
+ }
158
+
159
+ /** Thumbnail URL for structured data only; the page never requests it. */
160
+ export function buildYouTubeThumbnailUrl(id: string): string {
161
+ assertYouTubeVideoId(id);
162
+ return `https://i.ytimg.com/vi/${id}/hqdefault.jpg`;
163
+ }
@@ -1,4 +1,4 @@
1
- import { a as PublishedArticle, b as PublishedSite, c as PublishedAuthor } from './index-CKWrb9a4.js';
1
+ import { a as PublishedArticle, b as PublishedSite, c as PublishedAuthor } from './index-CmjKzrH-.js';
2
2
 
3
3
  /**
4
4
  * SEO utilities for canonical URL generation and validation.
@@ -173,9 +173,9 @@ interface SitemapOptions {
173
173
  includeTopicPages?: boolean;
174
174
  /** Whether to include series pages in sitemap */
175
175
  includeSeriesPages?: boolean;
176
- /** Whether to include pagination pages in sitemap */
176
+ /** @deprecated Pagination pages are never included. */
177
177
  includePaginationPages?: boolean;
178
- /** Number of articles per page for pagination calculation */
178
+ /** @deprecated Pagination pages are never included. */
179
179
  articlesPerPage?: number;
180
180
  /** Maximum number of sitemap entries (defaults to 50,000 per Google guidelines) */
181
181
  maxEntries?: number;
@@ -425,4 +425,5 @@ interface BlogSitemapConfig {
425
425
  */
426
426
  declare function createDynamicBlogSitemap(config: BlogSitemapConfig): () => Promise<NextjsSitemapEntry[]>;
427
427
 
428
- export { type BlogSitemapConfig as B, type CanonicalUrlOptions as C, type NextjsSitemapEntry as N, type SitemapChangeFreq as S, type SitemapData as a, type SitemapEntry as b, type SitemapOptions as c, createBlogSitemapRoute as d, createDynamicBlogSitemap as e, generateCanonicalUrl as f, generateBlogSitemapEntries as g, generateCanonicalUrlFromSlug as h, generateSitemap as i, generateSitemapIndex as j, getBaseUrl as k, isValidCanonicalUrl as l, normalizeSlug as m, normalizeDomain as n, sitemapToXml as s, updateArticleCanonicalUrl as u, validateSitemap as v };
428
+ export { createBlogSitemapRoute as d, createDynamicBlogSitemap as e, generateCanonicalUrl as f, generateBlogSitemapEntries as g, generateCanonicalUrlFromSlug as h, generateSitemap as i, generateSitemapIndex as j, getBaseUrl as k, isValidCanonicalUrl as l, normalizeSlug as m, normalizeDomain as n, sitemapToXml as s, updateArticleCanonicalUrl as u, validateSitemap as v };
429
+ export type { BlogSitemapConfig as B, CanonicalUrlOptions as C, NextjsSitemapEntry as N, SitemapChangeFreq as S, SitemapData as a, SitemapEntry as b, SitemapOptions as c };
@@ -1,4 +1,4 @@
1
- import { a as PublishedArticle, b as PublishedSite, c as PublishedAuthor } from './index-CKWrb9a4.mjs';
1
+ import { a as PublishedArticle, b as PublishedSite, c as PublishedAuthor } from './index-CmjKzrH-.mjs';
2
2
 
3
3
  /**
4
4
  * SEO utilities for canonical URL generation and validation.
@@ -173,9 +173,9 @@ interface SitemapOptions {
173
173
  includeTopicPages?: boolean;
174
174
  /** Whether to include series pages in sitemap */
175
175
  includeSeriesPages?: boolean;
176
- /** Whether to include pagination pages in sitemap */
176
+ /** @deprecated Pagination pages are never included. */
177
177
  includePaginationPages?: boolean;
178
- /** Number of articles per page for pagination calculation */
178
+ /** @deprecated Pagination pages are never included. */
179
179
  articlesPerPage?: number;
180
180
  /** Maximum number of sitemap entries (defaults to 50,000 per Google guidelines) */
181
181
  maxEntries?: number;
@@ -425,4 +425,5 @@ interface BlogSitemapConfig {
425
425
  */
426
426
  declare function createDynamicBlogSitemap(config: BlogSitemapConfig): () => Promise<NextjsSitemapEntry[]>;
427
427
 
428
- export { type BlogSitemapConfig as B, type CanonicalUrlOptions as C, type NextjsSitemapEntry as N, type SitemapChangeFreq as S, type SitemapData as a, type SitemapEntry as b, type SitemapOptions as c, createBlogSitemapRoute as d, createDynamicBlogSitemap as e, generateCanonicalUrl as f, generateBlogSitemapEntries as g, generateCanonicalUrlFromSlug as h, generateSitemap as i, generateSitemapIndex as j, getBaseUrl as k, isValidCanonicalUrl as l, normalizeSlug as m, normalizeDomain as n, sitemapToXml as s, updateArticleCanonicalUrl as u, validateSitemap as v };
428
+ export { createBlogSitemapRoute as d, createDynamicBlogSitemap as e, generateCanonicalUrl as f, generateBlogSitemapEntries as g, generateCanonicalUrlFromSlug as h, generateSitemap as i, generateSitemapIndex as j, getBaseUrl as k, isValidCanonicalUrl as l, normalizeSlug as m, normalizeDomain as n, sitemapToXml as s, updateArticleCanonicalUrl as u, validateSitemap as v };
429
+ export type { BlogSitemapConfig as B, CanonicalUrlOptions as C, NextjsSitemapEntry as N, SitemapChangeFreq as S, SitemapData as a, SitemapEntry as b, SitemapOptions as c };
@@ -1,2 +1,2 @@
1
- import './index-CKWrb9a4.mjs';
2
- export { B as BlogSitemapConfig, N as NextjsSitemapEntry, S as SitemapChangeFreq, a as SitemapData, b as SitemapEntry, c as SitemapOptions, d as createBlogSitemapRoute, e as createDynamicBlogSitemap, g as generateBlogSitemapEntries, i as generateSitemap, j as generateSitemapIndex, s as sitemapToXml, v as validateSitemap } from './sitemap-D3rS0iAF.mjs';
1
+ import './index-CmjKzrH-.mjs';
2
+ export { B as BlogSitemapConfig, N as NextjsSitemapEntry, S as SitemapChangeFreq, a as SitemapData, b as SitemapEntry, c as SitemapOptions, d as createBlogSitemapRoute, e as createDynamicBlogSitemap, g as generateBlogSitemapEntries, i as generateSitemap, j as generateSitemapIndex, s as sitemapToXml, v as validateSitemap } from './sitemap-D1eInRA1.mjs';
package/dist/sitemap.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import './index-CKWrb9a4.js';
2
- export { B as BlogSitemapConfig, N as NextjsSitemapEntry, S as SitemapChangeFreq, a as SitemapData, b as SitemapEntry, c as SitemapOptions, d as createBlogSitemapRoute, e as createDynamicBlogSitemap, g as generateBlogSitemapEntries, i as generateSitemap, j as generateSitemapIndex, s as sitemapToXml, v as validateSitemap } from './sitemap-BDT3d496.js';
1
+ import './index-CmjKzrH-.js';
2
+ export { B as BlogSitemapConfig, N as NextjsSitemapEntry, S as SitemapChangeFreq, a as SitemapData, b as SitemapEntry, c as SitemapOptions, d as createBlogSitemapRoute, e as createDynamicBlogSitemap, g as generateBlogSitemapEntries, i as generateSitemap, j as generateSitemapIndex, s as sitemapToXml, v as validateSitemap } from './sitemap-CEjqxRaQ.js';
package/dist/sitemap.js CHANGED
@@ -1,6 +1,6 @@
1
- 'use strict';function O(t){let e=new Map;for(let r of t)r.author?.slug&&e.set(r.author.slug,L(e.get(r.author.slug),r.author));return Array.from(e.values()).sort((r,i)=>r.name.localeCompare(i.name))}function L(t,e){return t?{...t,bio:t.bio??e.bio,avatarUrl:t.avatarUrl??e.avatarUrl,pronouns:t.pronouns??e.pronouns,socialLinks:t.socialLinks??e.socialLinks,toneAdjectives:t.toneAdjectives.length>0?t.toneAdjectives:e.toneAdjectives}:e}function R(t,e=true){let r=t.trim();return r=r.replace(/\/+$/,""),/^https?:\/\//.exec(r)||(r=`${e?"https":"http"}://${r}`),e&&r.startsWith("http://")&&(r=r.replace("http://","https://")),r}function k(t){let e=t.trim();if(!e)throw new Error("Slug cannot be empty");if(!/^[a-z0-9-]+$/.test(e))throw new Error("Slug can only contain lowercase letters, numbers, and hyphens");let i=e.replace(/-+/g,"-").replace(/^-+|-+$/g,"");if(!i)throw new Error("Slug cannot consist only of hyphens");return i}function _(t,e={}){let{forceHttps:r=true,removeTrailingSlash:i=true,domain:n=t.site.domain,pathPrefix:a="/blog"}=e;if(!n)throw new Error("Domain is required for canonical URL generation");if(!t.slug)throw new Error("Article slug is required for canonical URL generation");let l=R(n,r),P=k(t.slug),b=a.replace(/^(?!\/)/,"/").replace(/\/+$/,""),o=`${l}${b}/${P}`;return i&&(o=o.replace(/\/+$/,"")),o}function U(t){let e=new Map;for(let r of t)r.series&&(!e.has(r.series.slug)||r.series.articles.length>(e.get(r.series.slug)?.articles.length??0))&&e.set(r.series.slug,r.series);return Array.from(e.values()).sort((r,i)=>r.title.localeCompare(i.title))}function q(t){let e=new Map;for(let r of t)for(let i of r.topics??[])e.has(i.slug)||e.set(i.slug,i);return Array.from(e.values()).sort((r,i)=>r.name.localeCompare(i.name))}function D(t="/blog"){let e=t.trim();return !e||e==="/"?"":e.replace(/^(?!\/)/,"/").replace(/\/+$/,"")}function y(t,e){let r=D(t),i=e?.replace(/^\/+/,"").replace(/\/+$/,"");return i?`${r}/${i}`.replace(/\/+/g,"/"):r||"/"}function B(t,e){return y(t,`authors/${e}`)}function F(t,e){return y(t,`series/${e}`)}async function C(t,e,r=[],i={}){let{includeAuthorPages:n=true,includeTopicPages:a=true,includeSeriesPages:l=true,includePaginationPages:P=true,articlesPerPage:b=10,maxEntries:o=5e4,changeFrequency:u={},priority:c={}}=i,s=[],h=R(i.canonical?.domain??t.domain,i.canonical?.forceHttps??true),m=i.canonical?.pathPrefix??"/blog",v=r.length>0?r:O(e),S=q([...e]),x=U([...e]);if(s.push({url:`${h}${m}`,lastModified:f(e),changeFrequency:u.homePage??"daily",priority:c.homePage??1}),e.forEach(p=>{if(s.length>=o)return;let d=_(p,i.canonical);s.push({url:d,lastModified:p.updatedAt,changeFrequency:u.articles??"monthly",priority:c.articles??.8});}),a&&S.length>0&&(s.length<o&&s.push({url:`${h}${y(m,"topics")}`,lastModified:f(e),changeFrequency:u.topicPages??"weekly",priority:c.topicPages??.6}),S.forEach(p=>{if(s.length>=o)return;let d=e.filter(w=>(w.topics??[]).some(g=>g.slug===p.slug));s.push({url:`${h}${y(m,`topics/${p.slug}`)}`,lastModified:f(d),changeFrequency:u.topicPages??"weekly",priority:c.topicPages??.6});})),l&&x.length>0&&(s.length<o&&s.push({url:`${h}${y(m,"series")}`,lastModified:f(e),changeFrequency:u.seriesPages??"weekly",priority:c.seriesPages??.6}),x.forEach(p=>{if(s.length>=o)return;let d=e.filter(w=>w.series?.slug===p.slug);s.push({url:`${h}${F(m,p.slug)}`,lastModified:f(d),changeFrequency:u.seriesPages??"weekly",priority:c.seriesPages??.6});})),n&&v.length>0&&(s.length<o&&s.push({url:`${h}${y(m,"authors")}`,lastModified:f(e),changeFrequency:u.authorPages??"weekly",priority:c.authorPages??.6}),v.forEach(p=>{if(s.length>=o)return;let d=`${h}${B(m,p.slug)}`,w=e.filter(A=>A.author?.id===p.id),g=f(w);s.push({url:d,lastModified:g,changeFrequency:u.authorPages??"weekly",priority:c.authorPages??.6});})),P&&e.length>b){let p=Math.ceil(e.length/b);for(let d=2;d<=p&&!(s.length>=o);d++)s.push({url:`${h}${m}?page=${d}`,lastModified:f(e),changeFrequency:u.paginationPages??"daily",priority:c.paginationPages??.5});}return s}function te(t,e,r=[],i={}){let{includeAuthorPages:n=true,includeTopicPages:a=true,includeSeriesPages:l=true,includePaginationPages:P=true,articlesPerPage:b=10,maxEntries:o=5e4,changeFrequency:u={},priority:c={}}=i,s=[],h=R(i.canonical?.domain??t.domain,i.canonical?.forceHttps??true),m=i.canonical?.pathPrefix??"/blog",v=new Date().toISOString(),S=r.length>0?r:O(e),x=q([...e]),p=U([...e]),d=1;if(d+=e.length,a&&x.length>0&&(d+=x.length+1),l&&p.length>0&&(d+=p.length+1),n&&(d+=S.length>0?S.length+1:0),P&&e.length>b){let g=Math.ceil(e.length/b);d+=g-1;}let w=d>o;if(s.push({loc:`${h}${m}`,lastmod:f(e),changefreq:u.homePage??"daily",priority:c.homePage??1}),e.forEach(g=>{if(s.length>=o)return;let A=_(g,i.canonical);s.push({loc:A,lastmod:g.updatedAt,changefreq:u.articles??"monthly",priority:c.articles??.8});}),a&&x.length>0&&(s.length<o&&s.push({loc:`${h}${y(m,"topics")}`,lastmod:f(e),changefreq:u.topicPages??"weekly",priority:c.topicPages??.6}),x.forEach(g=>{if(s.length>=o)return;let A=e.filter($=>($.topics??[]).some(T=>T.slug===g.slug));s.push({loc:`${h}${y(m,`topics/${g.slug}`)}`,lastmod:f(A),changefreq:u.topicPages??"weekly",priority:c.topicPages??.6});})),l&&p.length>0&&(s.length<o&&s.push({loc:`${h}${y(m,"series")}`,lastmod:f(e),changefreq:u.seriesPages??"weekly",priority:c.seriesPages??.6}),p.forEach(g=>{if(s.length>=o)return;let A=e.filter($=>$.series?.slug===g.slug);s.push({loc:`${h}${F(m,g.slug)}`,lastmod:f(A),changefreq:u.seriesPages??"weekly",priority:c.seriesPages??.6});})),n&&S.length>0&&(s.length<o&&s.push({loc:`${h}${y(m,"authors")}`,lastmod:f(e),changefreq:u.authorPages??"weekly",priority:c.authorPages??.6}),S.forEach(g=>{if(s.length>=o)return;let A=`${h}${B(m,g.slug)}`,$=e.filter(M=>M.author?.id===g.id),T=f($);s.push({loc:A,lastmod:T,changefreq:u.authorPages??"weekly",priority:c.authorPages??.6});})),P&&e.length>b){let g=Math.ceil(e.length/b);for(let A=2;A<=g&&!(s.length>=o);A++)s.push({loc:`${h}${m}?page=${A}`,lastmod:f(e),changefreq:u.paginationPages??"daily",priority:c.paginationPages??.5});}return {entries:s,totalEntries:s.length,truncated:w,generatedAt:v,baseUrl:h}}function G(t){let e='<?xml version="1.0" encoding="UTF-8"?>',r='<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',i="</urlset>",n=t.entries.map(a=>{let l=[];return l.push(" <url>"),l.push(` <loc>${E(a.loc)}</loc>`),a.lastmod&&l.push(` <lastmod>${E(a.lastmod)}</lastmod>`),a.changefreq&&l.push(` <changefreq>${E(a.changefreq)}</changefreq>`),a.priority!==void 0&&l.push(` <priority>${a.priority.toFixed(1)}</priority>`),l.push(" </url>"),l.join(`
1
+ 'use strict';function v(t){let e=new Map;for(let r of t)r.author?.slug&&e.set(r.author.slug,F(e.get(r.author.slug),r.author));return Array.from(e.values()).sort((r,i)=>r.name.localeCompare(i.name))}function F(t,e){return t?{...t,bio:t.bio??e.bio,avatarUrl:t.avatarUrl??e.avatarUrl,pronouns:t.pronouns??e.pronouns,socialLinks:t.socialLinks??e.socialLinks,toneAdjectives:t.toneAdjectives.length>0?t.toneAdjectives:e.toneAdjectives}:e}function w(t,e=true){let r=t.trim();return r=r.replace(/\/+$/,""),/^https?:\/\//.exec(r)||(r=`${e?"https":"http"}://${r}`),e&&r.startsWith("http://")&&(r=r.replace("http://","https://")),r}function M(t){let e=t.trim();if(!e)throw new Error("Slug cannot be empty");if(!/^[a-z0-9-]+$/.test(e))throw new Error("Slug can only contain lowercase letters, numbers, and hyphens");let i=e.replace(/-+/g,"-").replace(/^-+|-+$/g,"");if(!i)throw new Error("Slug cannot consist only of hyphens");return i}function T(t,e={}){let{forceHttps:r=true,removeTrailingSlash:i=true,domain:n=t.site.domain,pathPrefix:o="/blog"}=e;if(!n)throw new Error("Domain is required for canonical URL generation");if(!t.slug)throw new Error("Article slug is required for canonical URL generation");let u=w(n,r),l=M(t.slug),c=o.replace(/^(?!\/)/,"/").replace(/\/+$/,""),a=`${u}${c}/${l}`;return i&&(a=a.replace(/\/+$/,"")),a}function O(t){let e=new Map;for(let r of t)r.series&&(!e.has(r.series.slug)||r.series.articles.length>(e.get(r.series.slug)?.articles.length??0))&&e.set(r.series.slug,r.series);return Array.from(e.values()).sort((r,i)=>r.title.localeCompare(i.title))}function _(t){let e=new Map;for(let r of t)for(let i of r.topics??[])e.has(i.slug)||e.set(i.slug,i);return Array.from(e.values()).sort((r,i)=>r.name.localeCompare(i.name))}function L(t="/blog"){let e=t.trim();return !e||e==="/"?"":e.replace(/^(?!\/)/,"/").replace(/\/+$/,"")}function P(t,e){let r=L(t),i=e?.replace(/^\/+/,"").replace(/\/+$/,"");return i?`${r}/${i}`.replace(/\/+/g,"/"):r||"/"}function U(t,e){return P(t,`authors/${e}`)}function B(t,e){return P(t,`series/${e}`)}async function q(t,e,r=[],i={}){let{includeAuthorPages:n=true,includeTopicPages:o=true,includeSeriesPages:u=true,maxEntries:l=5e4,changeFrequency:c={},priority:a={}}=i,s=[],h=w(i.canonical?.domain??t.domain,i.canonical?.forceHttps??true),p=i.canonical?.pathPrefix??"/blog",$=r.length>0?r:v(e),b=_([...e]),y=O([...e]);return s.push({url:`${h}${p}`,lastModified:m(e),changeFrequency:c.homePage??"daily",priority:a.homePage??1}),e.forEach(g=>{if(s.length>=l)return;let f=T(g,i.canonical);s.push({url:f,lastModified:g.updatedAt,changeFrequency:c.articles??"monthly",priority:a.articles??.8});}),o&&b.length>0&&(s.length<l&&s.push({url:`${h}${P(p,"topics")}`,lastModified:m(e),changeFrequency:c.topicPages??"weekly",priority:a.topicPages??.6}),b.forEach(g=>{if(s.length>=l)return;let f=e.filter(S=>(S.topics??[]).some(d=>d.slug===g.slug));s.push({url:`${h}${P(p,`topics/${g.slug}`)}`,lastModified:m(f),changeFrequency:c.topicPages??"weekly",priority:a.topicPages??.6});})),u&&y.length>0&&(s.length<l&&s.push({url:`${h}${P(p,"series")}`,lastModified:m(e),changeFrequency:c.seriesPages??"weekly",priority:a.seriesPages??.6}),y.forEach(g=>{if(s.length>=l)return;let f=e.filter(S=>S.series?.slug===g.slug);s.push({url:`${h}${B(p,g.slug)}`,lastModified:m(f),changeFrequency:c.seriesPages??"weekly",priority:a.seriesPages??.6});})),n&&$.length>0&&(s.length<l&&s.push({url:`${h}${P(p,"authors")}`,lastModified:m(e),changeFrequency:c.authorPages??"weekly",priority:a.authorPages??.6}),$.forEach(g=>{if(s.length>=l)return;let f=`${h}${U(p,g.slug)}`,S=e.filter(A=>A.author?.id===g.id),d=m(S);s.push({url:f,lastModified:d,changeFrequency:c.authorPages??"weekly",priority:a.authorPages??.6});})),s}function K(t,e,r=[],i={}){let{includeAuthorPages:n=true,includeTopicPages:o=true,includeSeriesPages:u=true,maxEntries:l=5e4,changeFrequency:c={},priority:a={}}=i,s=[],h=w(i.canonical?.domain??t.domain,i.canonical?.forceHttps??true),p=i.canonical?.pathPrefix??"/blog",$=new Date().toISOString(),b=r.length>0?r:v(e),y=_([...e]),g=O([...e]),f=1;f+=e.length,o&&y.length>0&&(f+=y.length+1),u&&g.length>0&&(f+=g.length+1),n&&(f+=b.length>0?b.length+1:0);let S=f>l;return s.push({loc:`${h}${p}`,lastmod:m(e),changefreq:c.homePage??"daily",priority:a.homePage??1}),e.forEach(d=>{if(s.length>=l)return;let A=T(d,i.canonical);s.push({loc:A,lastmod:d.updatedAt,changefreq:c.articles??"monthly",priority:a.articles??.8});}),o&&y.length>0&&(s.length<l&&s.push({loc:`${h}${P(p,"topics")}`,lastmod:m(e),changefreq:c.topicPages??"weekly",priority:a.topicPages??.6}),y.forEach(d=>{if(s.length>=l)return;let A=e.filter(x=>(x.topics??[]).some(E=>E.slug===d.slug));s.push({loc:`${h}${P(p,`topics/${d.slug}`)}`,lastmod:m(A),changefreq:c.topicPages??"weekly",priority:a.topicPages??.6});})),u&&g.length>0&&(s.length<l&&s.push({loc:`${h}${P(p,"series")}`,lastmod:m(e),changefreq:c.seriesPages??"weekly",priority:a.seriesPages??.6}),g.forEach(d=>{if(s.length>=l)return;let A=e.filter(x=>x.series?.slug===d.slug);s.push({loc:`${h}${B(p,d.slug)}`,lastmod:m(A),changefreq:c.seriesPages??"weekly",priority:a.seriesPages??.6});})),n&&b.length>0&&(s.length<l&&s.push({loc:`${h}${P(p,"authors")}`,lastmod:m(e),changefreq:c.authorPages??"weekly",priority:a.authorPages??.6}),b.forEach(d=>{if(s.length>=l)return;let A=`${h}${U(p,d.slug)}`,x=e.filter(C=>C.author?.id===d.id),E=m(x);s.push({loc:A,lastmod:E,changefreq:c.authorPages??"weekly",priority:a.authorPages??.6});})),{entries:s,totalEntries:s.length,truncated:S,generatedAt:$,baseUrl:h}}function k(t){let e='<?xml version="1.0" encoding="UTF-8"?>',r='<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',i="</urlset>",n=t.entries.map(o=>{let u=[];return u.push(" <url>"),u.push(` <loc>${R(o.loc)}</loc>`),o.lastmod&&u.push(` <lastmod>${R(o.lastmod)}</lastmod>`),o.changefreq&&u.push(` <changefreq>${R(o.changefreq)}</changefreq>`),o.priority!==void 0&&u.push(` <priority>${o.priority.toFixed(1)}</priority>`),u.push(" </url>"),u.join(`
2
2
  `)});return [e,r,...n,i].join(`
3
- `)}function re(t,e){let r='<?xml version="1.0" encoding="UTF-8"?>',i='<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',n="</sitemapindex>",a=t.map(l=>{let P=[];return P.push(" <sitemap>"),P.push(` <loc>${E(l.url)}</loc>`),l.lastmod&&P.push(` <lastmod>${E(l.lastmod)}</lastmod>`),P.push(" </sitemap>"),P.join(`
4
- `)});return [r,i,...a,n].join(`
5
- `)}function ie(t){let e=[],r=[];t.entries.length===0?e.push("Sitemap must contain at least one URL"):t.entries.length>5e4&&e.push("Sitemap contains more than 50,000 URLs (Google limit)"),t.truncated&&r.push("Sitemap was truncated due to size limits"),t.entries.forEach((n,a)=>{try{new URL(n.loc);}catch{e.push(`Invalid URL at entry ${a+1}: ${n.loc}`);}n.priority!==void 0&&(n.priority<0||n.priority>1)&&e.push(`Invalid priority at entry ${a+1}: must be between 0.0 and 1.0`),n.lastmod&&!I(n.lastmod)&&e.push(`Invalid lastmod date at entry ${a+1}: ${n.lastmod}`),n.loc.length>2048&&r.push(`URL at entry ${a+1} exceeds 2048 character limit`);});let i=new Map;return t.entries.forEach(n=>{let a=i.get(n.loc)??0;i.set(n.loc,a+1);}),i.forEach((n,a)=>{n>1&&e.push(`Duplicate URL found: ${a} (appears ${n} times)`);}),{valid:e.length===0,errors:e,warnings:r}}function f(t){return t.length===0?new Date().toISOString():t.reduce((r,i)=>{let n=new Date(i.updatedAt);return n>r?n:r},new Date(t[0].updatedAt)).toISOString()}function se(t,e,r=[],i={}){return async function(){let l=(await C(t,e,r,i)).map(o=>({loc:o.url,lastmod:typeof o.lastModified=="string"?o.lastModified:o.lastModified?.toISOString(),changefreq:o.changeFrequency,priority:o.priority})),P={entries:l,totalEntries:l.length,generatedAt:new Date().toISOString(),baseUrl:R(i.canonical?.domain??t.domain,i.canonical?.forceHttps??true)},b=G(P);return new Response(b,{headers:{"Content-Type":"application/xml","Cache-Control":"public, max-age=3600, s-maxage=3600"}})}}function ne(t){return async function(){let r=[...t.staticRoutes??[],...t.getStaticRoutes?await t.getStaticRoutes():[]],i=await t.getSite(),n=await t.getArticles(),a=t.getAuthors?await t.getAuthors():[];return [...r,...await C(i,n,a,t.options)]}}function E(t){let e={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;"};return t.replace(/[&<>"']/g,r=>e[r]??r)}function I(t){try{let e=new Date(t),r=e.toISOString(),i=t.endsWith("Z")&&!t.includes(".")?t.replace("Z",".000Z"):t;return r===i&&!isNaN(e.getTime())}catch{return false}}exports.createBlogSitemapRoute=se;exports.createDynamicBlogSitemap=ne;exports.generateBlogSitemapEntries=C;exports.generateSitemap=te;exports.generateSitemapIndex=re;exports.sitemapToXml=G;exports.validateSitemap=ie;//# sourceMappingURL=sitemap.js.map
3
+ `)}function ee(t,e){let r='<?xml version="1.0" encoding="UTF-8"?>',i='<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',n="</sitemapindex>",o=t.map(u=>{let l=[];return l.push(" <sitemap>"),l.push(` <loc>${R(u.url)}</loc>`),u.lastmod&&l.push(` <lastmod>${R(u.lastmod)}</lastmod>`),l.push(" </sitemap>"),l.join(`
4
+ `)});return [r,i,...o,n].join(`
5
+ `)}function te(t){let e=[],r=[];t.entries.length===0?e.push("Sitemap must contain at least one URL"):t.entries.length>5e4&&e.push("Sitemap contains more than 50,000 URLs (Google limit)"),t.truncated&&r.push("Sitemap was truncated due to size limits"),t.entries.forEach((n,o)=>{try{new URL(n.loc);}catch{e.push(`Invalid URL at entry ${o+1}: ${n.loc}`);}n.priority!==void 0&&(n.priority<0||n.priority>1)&&e.push(`Invalid priority at entry ${o+1}: must be between 0.0 and 1.0`),n.lastmod&&!D(n.lastmod)&&e.push(`Invalid lastmod date at entry ${o+1}: ${n.lastmod}`),n.loc.length>2048&&r.push(`URL at entry ${o+1} exceeds 2048 character limit`);});let i=new Map;return t.entries.forEach(n=>{let o=i.get(n.loc)??0;i.set(n.loc,o+1);}),i.forEach((n,o)=>{n>1&&e.push(`Duplicate URL found: ${o} (appears ${n} times)`);}),{valid:e.length===0,errors:e,warnings:r}}function m(t){return t.length===0?new Date().toISOString():t.reduce((r,i)=>{let n=new Date(i.updatedAt);return n>r?n:r},new Date(t[0].updatedAt)).toISOString()}function re(t,e,r=[],i={}){return async function(){let u=(await q(t,e,r,i)).map(a=>({loc:a.url,lastmod:typeof a.lastModified=="string"?a.lastModified:a.lastModified?.toISOString(),changefreq:a.changeFrequency,priority:a.priority})),l={entries:u,totalEntries:u.length,generatedAt:new Date().toISOString(),baseUrl:w(i.canonical?.domain??t.domain,i.canonical?.forceHttps??true)},c=k(l);return new Response(c,{headers:{"Content-Type":"application/xml","Cache-Control":"public, max-age=3600, s-maxage=3600"}})}}function ie(t){return async function(){let r=[...t.staticRoutes??[],...t.getStaticRoutes?await t.getStaticRoutes():[]],i=await t.getSite(),n=await t.getArticles(),o=t.getAuthors?await t.getAuthors():[];return [...r,...await q(i,n,o,t.options)]}}function R(t){let e={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;"};return t.replace(/[&<>"']/g,r=>e[r]??r)}function D(t){try{let e=new Date(t),r=e.toISOString(),i=t.endsWith("Z")&&!t.includes(".")?t.replace("Z",".000Z"):t;return r===i&&!isNaN(e.getTime())}catch{return false}}exports.createBlogSitemapRoute=re;exports.createDynamicBlogSitemap=ie;exports.generateBlogSitemapEntries=q;exports.generateSitemap=K;exports.generateSitemapIndex=ee;exports.sitemapToXml=k;exports.validateSitemap=te;//# sourceMappingURL=sitemap.js.map
6
6
  //# sourceMappingURL=sitemap.js.map