@xexr/blog-platform 1.0.17 → 1.1.1

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/dist/lib/auth.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type NextRequest } from "next/server";
1
+ import { type NextRequest } from "next/server.js";
2
2
  import { createHash } from "crypto";
3
3
  import type { PublishResult } from "@/types/contracts";
4
4
 
package/dist/lib/cors.ts CHANGED
@@ -5,8 +5,8 @@
5
5
  * for cross-origin requests from Bloggy core applications.
6
6
  */
7
7
 
8
- import type { NextRequest } from "next/server";
9
- import { NextResponse } from "next/server";
8
+ import type { NextRequest } from "next/server.js";
9
+ import { NextResponse } from "next/server.js";
10
10
  import { getAPISecurityHeaders, type SecurityConfig } from "./security-headers";
11
11
 
12
12
  /**
@@ -52,6 +52,16 @@ export interface BlogPlatformStorage {
52
52
  idempotencyKey: string,
53
53
  result: PublishResult,
54
54
  ): Promise<void>;
55
+
56
+ /**
57
+ * Store platform metadata/configuration by key.
58
+ */
59
+ setMetadata(key: string, value: unknown): Promise<void>;
60
+
61
+ /**
62
+ * Retrieve platform metadata/configuration by key.
63
+ */
64
+ getMetadata<T = unknown>(key: string): Promise<T | null>;
55
65
  }
56
66
 
57
67
  /**
@@ -61,6 +71,7 @@ export interface BlogPlatformStorage {
61
71
  export class InMemoryStorage implements BlogPlatformStorage {
62
72
  private articles = new Map<string, PublishedArticle>();
63
73
  private operations = new Map<string, PublishResult>();
74
+ private metadata = new Map<string, unknown>();
64
75
 
65
76
  async saveArticle(
66
77
  article: PublishedArticle,
@@ -226,6 +237,15 @@ export class InMemoryStorage implements BlogPlatformStorage {
226
237
  ): Promise<void> {
227
238
  this.operations.set(idempotencyKey, result);
228
239
  }
240
+
241
+ async setMetadata(key: string, value: unknown): Promise<void> {
242
+ this.metadata.set(key, value);
243
+ }
244
+
245
+ async getMetadata<T = unknown>(key: string): Promise<T | null> {
246
+ if (!this.metadata.has(key)) return null;
247
+ return this.metadata.get(key) as T;
248
+ }
229
249
  }
230
250
 
231
251
  type GlobalWithStorage = typeof globalThis & {
@@ -5,7 +5,7 @@
5
5
  * web vulnerabilities including XSS, clickjacking, and content injection.
6
6
  */
7
7
 
8
- import type { NextRequest } from "next/server";
8
+ import type { NextRequest } from "next/server.js";
9
9
 
10
10
  /**
11
11
  * Security headers configuration interface
@@ -53,6 +53,28 @@ export const bpOperations = sqliteTable("bp_operations", {
53
53
  expiresAt: integer("expires_at", { mode: "timestamp_ms" }),
54
54
  });
55
55
 
56
+ export const bpMetadata = sqliteTable("bp_metadata", {
57
+ key: text("key").primaryKey(),
58
+ valueJson: text("value_json").notNull(),
59
+ updatedAt: integer("updated_at", { mode: "timestamp_ms" }).notNull(),
60
+ });
61
+
62
+ export const BLOG_PLATFORM_SCHEMA_VERSION = 2;
63
+
64
+ export const BLOG_PLATFORM_TABLE_NAMES = [
65
+ "bp_articles",
66
+ "bp_operations",
67
+ "bp_metadata",
68
+ ] as const;
69
+
70
+ export type BlogPlatformTableName = (typeof BLOG_PLATFORM_TABLE_NAMES)[number];
71
+
72
+ export const blogPlatformDrizzleSchema = {
73
+ bpArticles,
74
+ bpOperations,
75
+ bpMetadata,
76
+ } as const;
77
+
56
78
  type ArticleRow = InferSelectModel<typeof bpArticles>;
57
79
 
58
80
  function toRow(article: PublishedArticle): ArticleRow {
@@ -76,6 +98,7 @@ function toRow(article: PublishedArticle): ArticleRow {
76
98
  metaJson: JSON.stringify({
77
99
  ...article.meta,
78
100
  __bloggyTopics: article.topics ?? [],
101
+ __bloggyCta: article.cta ?? null,
79
102
  }),
80
103
  version: article.version,
81
104
  };
@@ -101,24 +124,36 @@ function fromRow(row: ArticleRow): PublishedArticle {
101
124
  ? (JSON.parse(row.authorJson) as PublishedAuthor)
102
125
  : null,
103
126
  site: JSON.parse(row.siteJson) as PublishedSite,
127
+ cta: extractCta(row.metaJson),
104
128
  topics: extractTopics(row.metaJson),
105
129
  meta: extractMeta(row.metaJson),
106
130
  version: row.version,
107
131
  };
108
132
  }
109
133
 
134
+ type StoredArticleMeta = PublishedArticle["meta"] & {
135
+ __bloggyTopics?: PublishedArticle["topics"];
136
+ __bloggyCta?: PublishedArticle["cta"] | null;
137
+ };
138
+
139
+ function parseStoredMeta(metaJson: string): StoredArticleMeta {
140
+ return JSON.parse(metaJson) as StoredArticleMeta;
141
+ }
142
+
110
143
  function extractTopics(metaJson: string): PublishedArticle["topics"] {
111
- const parsed = JSON.parse(metaJson) as PublishedArticle["meta"] & {
112
- __bloggyTopics?: PublishedArticle["topics"];
113
- };
144
+ const parsed = parseStoredMeta(metaJson);
114
145
  return parsed.__bloggyTopics ?? [];
115
146
  }
116
147
 
148
+ function extractCta(metaJson: string): PublishedArticle["cta"] {
149
+ const parsed = parseStoredMeta(metaJson);
150
+ return parsed.__bloggyCta ?? undefined;
151
+ }
152
+
117
153
  function extractMeta(metaJson: string): PublishedArticle["meta"] {
118
- const parsed = JSON.parse(metaJson) as PublishedArticle["meta"] & {
119
- __bloggyTopics?: PublishedArticle["topics"];
120
- };
154
+ const parsed = parseStoredMeta(metaJson);
121
155
  delete parsed.__bloggyTopics;
156
+ delete parsed.__bloggyCta;
122
157
  const meta = parsed;
123
158
  return meta;
124
159
  }
@@ -128,6 +163,38 @@ export interface DrizzleStorageOptions {
128
163
  operationTtlMs?: number;
129
164
  }
130
165
 
166
+ export interface BlogPlatformSchemaCheckResult {
167
+ ok: boolean;
168
+ schemaVersion: number;
169
+ requiredTables: readonly BlogPlatformTableName[];
170
+ presentTables: BlogPlatformTableName[];
171
+ missingTables: BlogPlatformTableName[];
172
+ setupSql: string[];
173
+ }
174
+
175
+ export class BlogPlatformSchemaError extends Error {
176
+ readonly code = "BLOG_PLATFORM_SCHEMA_MISSING";
177
+ readonly missingTables: BlogPlatformTableName[];
178
+ readonly setupSql: string[];
179
+
180
+ constructor(
181
+ missingTables: BlogPlatformTableName[],
182
+ options: { cause?: unknown } = {},
183
+ ) {
184
+ super(
185
+ `Blog platform database schema is missing required table(s): ${missingTables.join(
186
+ ", ",
187
+ )}. Run the @xexr/blog-platform schema migration/setup for the current package version.`,
188
+ );
189
+ this.name = "BlogPlatformSchemaError";
190
+ this.missingTables = missingTables;
191
+ this.setupSql = missingTables.map(getCreateTableSqlForName);
192
+ if (options.cause !== undefined) {
193
+ this.cause = options.cause;
194
+ }
195
+ }
196
+ }
197
+
131
198
  /**
132
199
  * Drizzle-backed durable storage adapter.
133
200
  * Pass a Drizzle Database instance created with drizzle-orm/libsql.
@@ -374,18 +441,81 @@ export class DrizzleStorage implements BlogPlatformStorage {
374
441
  })
375
442
  .run();
376
443
  }
444
+
445
+ async setMetadata(key: string, value: unknown): Promise<void> {
446
+ const now = new Date();
447
+ try {
448
+ await this.db.delete(bpMetadata).where(eq(bpMetadata.key, key)).run();
449
+ await this.db
450
+ .insert(bpMetadata)
451
+ .values({
452
+ key,
453
+ valueJson: JSON.stringify(value),
454
+ updatedAt: now,
455
+ })
456
+ .run();
457
+ } catch (error) {
458
+ throwSchemaErrorForMissingTable(error, "bp_metadata");
459
+ }
460
+ }
461
+
462
+ async getMetadata<T = unknown>(key: string): Promise<T | null> {
463
+ let rows: { valueJson: string }[];
464
+ try {
465
+ rows = await this.db
466
+ .select({ valueJson: bpMetadata.valueJson })
467
+ .from(bpMetadata)
468
+ .where(eq(bpMetadata.key, key))
469
+ .limit(1);
470
+ } catch (error) {
471
+ throwSchemaErrorForMissingTable(error, "bp_metadata");
472
+ }
473
+ const row = rows[0];
474
+ if (!row) return null;
475
+ try {
476
+ return JSON.parse(row.valueJson) as T;
477
+ } catch {
478
+ return null;
479
+ }
480
+ }
377
481
  }
378
482
 
379
- /**
380
- * Helper: Return SQL strings for creating required tables.
381
- * Consumers can embed these in their migrations.
382
- */
383
- export function getDrizzleAdapterSql(): {
384
- createArticles: string;
385
- createOperations: string;
386
- } {
483
+ export async function checkDrizzleAdapterSchema(
484
+ db: LibSQLDatabase,
485
+ ): Promise<BlogPlatformSchemaCheckResult> {
486
+ const rows = await db.all<{ name: string }>(sql`
487
+ SELECT name
488
+ FROM sqlite_master
489
+ WHERE type = 'table'
490
+ `);
491
+ const presentTableNames = new Set(rows.map((row) => row.name));
492
+ const presentTables = BLOG_PLATFORM_TABLE_NAMES.filter((tableName) =>
493
+ presentTableNames.has(tableName),
494
+ );
495
+ const missingTables = BLOG_PLATFORM_TABLE_NAMES.filter(
496
+ (tableName) => !presentTableNames.has(tableName),
497
+ );
498
+
387
499
  return {
388
- createArticles: `CREATE TABLE IF NOT EXISTS bp_articles (
500
+ ok: missingTables.length === 0,
501
+ schemaVersion: BLOG_PLATFORM_SCHEMA_VERSION,
502
+ requiredTables: BLOG_PLATFORM_TABLE_NAMES,
503
+ presentTables,
504
+ missingTables,
505
+ setupSql: missingTables.map(getCreateTableSqlForName),
506
+ };
507
+ }
508
+
509
+ export async function ensureDrizzleAdapterSchema(
510
+ db: LibSQLDatabase,
511
+ ): Promise<void> {
512
+ const result = await checkDrizzleAdapterSchema(db);
513
+ if (!result.ok) {
514
+ throw new BlogPlatformSchemaError(result.missingTables);
515
+ }
516
+ }
517
+
518
+ const CREATE_ARTICLES_TABLE_SQL = `CREATE TABLE IF NOT EXISTS bp_articles (
389
519
  id TEXT PRIMARY KEY,
390
520
  slug TEXT NOT NULL UNIQUE,
391
521
  title TEXT NOT NULL,
@@ -404,12 +534,93 @@ export function getDrizzleAdapterSql(): {
404
534
  site_json TEXT NOT NULL,
405
535
  meta_json TEXT NOT NULL,
406
536
  version INTEGER NOT NULL
407
- );`,
408
- createOperations: `CREATE TABLE IF NOT EXISTS bp_operations (
537
+ );`;
538
+
539
+ const CREATE_OPERATIONS_TABLE_SQL = `CREATE TABLE IF NOT EXISTS bp_operations (
409
540
  key TEXT PRIMARY KEY,
410
541
  result_json TEXT NOT NULL,
411
542
  created_at INTEGER NOT NULL,
412
543
  expires_at INTEGER
413
- );`,
544
+ );`;
545
+
546
+ const CREATE_METADATA_TABLE_SQL = `CREATE TABLE IF NOT EXISTS bp_metadata (
547
+ key TEXT PRIMARY KEY,
548
+ value_json TEXT NOT NULL,
549
+ updated_at INTEGER NOT NULL
550
+ );`;
551
+
552
+ function getCreateTableSqlForName(tableName: BlogPlatformTableName): string {
553
+ switch (tableName) {
554
+ case "bp_articles":
555
+ return CREATE_ARTICLES_TABLE_SQL;
556
+ case "bp_operations":
557
+ return CREATE_OPERATIONS_TABLE_SQL;
558
+ case "bp_metadata":
559
+ return CREATE_METADATA_TABLE_SQL;
560
+ }
561
+ }
562
+
563
+ function throwSchemaErrorForMissingTable(
564
+ error: unknown,
565
+ tableName: BlogPlatformTableName,
566
+ ): never {
567
+ if (isMissingTableError(error, tableName)) {
568
+ throw new BlogPlatformSchemaError([tableName], { cause: error });
569
+ }
570
+ throw error;
571
+ }
572
+
573
+ function isMissingTableError(
574
+ error: unknown,
575
+ tableName: BlogPlatformTableName,
576
+ ): boolean {
577
+ return getErrorMessages(error).some(
578
+ (message) =>
579
+ message.includes(`no such table: ${tableName}`) ||
580
+ message.includes(`no such table: main.${tableName}`),
581
+ );
582
+ }
583
+
584
+ function getErrorMessages(error: unknown): string[] {
585
+ if (!error || typeof error !== "object") {
586
+ return [String(error)];
587
+ }
588
+
589
+ const messages: string[] = [];
590
+ if (error instanceof Error) {
591
+ messages.push(error.message);
592
+ }
593
+
594
+ const cause = (error as { cause?: unknown }).cause;
595
+ if (cause !== undefined) {
596
+ messages.push(...getErrorMessages(cause));
597
+ }
598
+
599
+ return messages;
600
+ }
601
+
602
+ /**
603
+ * Helper: Return SQL strings for creating required tables.
604
+ * Consumers can embed these in their migrations.
605
+ */
606
+ export function getDrizzleAdapterSql(): {
607
+ schemaVersion: number;
608
+ requiredTableNames: readonly BlogPlatformTableName[];
609
+ createAll: string[];
610
+ createArticles: string;
611
+ createOperations: string;
612
+ createMetadata: string;
613
+ } {
614
+ return {
615
+ schemaVersion: BLOG_PLATFORM_SCHEMA_VERSION,
616
+ requiredTableNames: BLOG_PLATFORM_TABLE_NAMES,
617
+ createAll: [
618
+ CREATE_ARTICLES_TABLE_SQL,
619
+ CREATE_OPERATIONS_TABLE_SQL,
620
+ CREATE_METADATA_TABLE_SQL,
621
+ ],
622
+ createArticles: CREATE_ARTICLES_TABLE_SQL,
623
+ createOperations: CREATE_OPERATIONS_TABLE_SQL,
624
+ createMetadata: CREATE_METADATA_TABLE_SQL,
414
625
  };
415
626
  }
@@ -522,7 +522,12 @@ export function validateStructuredData(schema: BaseStructuredData): {
522
522
  export function structuredDataToJsonLd(
523
523
  schema: BaseStructuredData | BaseStructuredData[],
524
524
  ): string {
525
- return JSON.stringify(schema, null, 0);
525
+ return JSON.stringify(schema, null, 0)
526
+ .replace(/</g, "\\u003c")
527
+ .replace(/>/g, "\\u003e")
528
+ .replace(/&/g, "\\u0026")
529
+ .replace(/\u2028/g, "\\u2028")
530
+ .replace(/\u2029/g, "\\u2029");
526
531
  }
527
532
 
528
533
  /**
package/dist/sitemap.js CHANGED
@@ -1,6 +1,6 @@
1
- 'use strict';function O(e){let t=new Map;for(let r of e)r.author?.slug&&t.set(r.author.slug,q(t.get(r.author.slug),r.author));return Array.from(t.values()).sort((r,i)=>r.name.localeCompare(i.name))}function q(e,t){return e?{...e,bio:e.bio??t.bio,avatarUrl:e.avatarUrl??t.avatarUrl,pronouns:e.pronouns??t.pronouns,socialLinks:e.socialLinks??t.socialLinks,toneAdjectives:e.toneAdjectives.length>0?e.toneAdjectives:t.toneAdjectives}:t}function x(e,t=true){let r=e.trim();return r=r.replace(/\/+$/,""),/^https?:\/\//.exec(r)||(r=`${t?"https":"http"}://${r}`),t&&r.startsWith("http://")&&(r=r.replace("http://","https://")),r}function F(e){let t=e.trim();if(!t)throw new Error("Slug cannot be empty");if(!/^[a-z0-9-]+$/.test(t))throw new Error("Slug can only contain lowercase letters, numbers, and hyphens");let i=t.replace(/-+/g,"-").replace(/^-+|-+$/g,"");if(!i)throw new Error("Slug cannot consist only of hyphens");return i}function _(e,t={}){let{forceHttps:r=true,removeTrailingSlash:i=true,domain:o=e.site.domain,pathPrefix:n="/blog"}=t;if(!o)throw new Error("Domain is required for canonical URL generation");if(!e.slug)throw new Error("Article slug is required for canonical URL generation");let l=x(o,r),u=F(e.slug),c=n.replace(/^(?!\/)/,"/").replace(/\/+$/,""),a=`${l}${c}/${u}`;return i&&(a=a.replace(/\/+$/,"")),a}function T(e){let t=new Map;for(let r of e)for(let i of r.topics??[])t.has(i.slug)||t.set(i.slug,i);return Array.from(t.values()).sort((r,i)=>r.name.localeCompare(i.name))}function L(e="/blog"){let t=e.trim();return !t||t==="/"?"":t.replace(/^(?!\/)/,"/").replace(/\/+$/,"")}function b(e,t){let r=L(e),i=t?.replace(/^\/+/,"").replace(/\/+$/,"");return i?`${r}/${i}`.replace(/\/+/g,"/"):r||"/"}function v(e,t){return b(e,`authors/${t}`)}async function U(e,t,r=[],i={}){let{includeAuthorPages:o=true,includeTopicPages:n=true,includePaginationPages:l=true,articlesPerPage:u=10,maxEntries:c=5e4,changeFrequency:a={},priority:g={}}=i,s=[],m=x(i.canonical?.domain??e.domain,i.canonical?.forceHttps??true),f=i.canonical?.pathPrefix??"/blog",R=r.length>0?r:O(t),y=T([...t]);if(s.push({url:`${m}${f}`,lastModified:P(t),changeFrequency:a.homePage??"daily",priority:g.homePage??1}),t.forEach(p=>{if(s.length>=c)return;let d=_(p,i.canonical);s.push({url:d,lastModified:p.updatedAt,changeFrequency:a.articles??"monthly",priority:g.articles??.8});}),n&&y.length>0&&(s.length<c&&s.push({url:`${m}${b(f,"topics")}`,lastModified:P(t),changeFrequency:a.topicPages??"weekly",priority:g.topicPages??.6}),y.forEach(p=>{if(s.length>=c)return;let d=t.filter(S=>(S.topics??[]).some(h=>h.slug===p.slug));s.push({url:`${m}${b(f,`topics/${p.slug}`)}`,lastModified:P(d),changeFrequency:a.topicPages??"weekly",priority:g.topicPages??.6});})),o&&R.length>0&&(s.length<c&&s.push({url:`${m}${b(f,"authors")}`,lastModified:P(t),changeFrequency:a.authorPages??"weekly",priority:g.authorPages??.6}),R.forEach(p=>{if(s.length>=c)return;let d=`${m}${v(f,p.slug)}`,S=t.filter(A=>A.author?.id===p.id),h=P(S);s.push({url:d,lastModified:h,changeFrequency:a.authorPages??"weekly",priority:g.authorPages??.6});})),l&&t.length>u){let p=Math.ceil(t.length/u);for(let d=2;d<=p&&!(s.length>=c);d++)s.push({url:`${m}${f}?page=${d}`,lastModified:P(t),changeFrequency:a.paginationPages??"daily",priority:g.paginationPages??.5});}return s}function X(e,t,r=[],i={}){let{includeAuthorPages:o=true,includeTopicPages:n=true,includePaginationPages:l=true,articlesPerPage:u=10,maxEntries:c=5e4,changeFrequency:a={},priority:g={}}=i,s=[],m=x(i.canonical?.domain??e.domain,i.canonical?.forceHttps??true),f=i.canonical?.pathPrefix??"/blog",R=new Date().toISOString(),y=r.length>0?r:O(t),p=T([...t]),d=1;if(d+=t.length,n&&p.length>0&&(d+=p.length+1),o&&(d+=y.length>0?y.length+1:0),l&&t.length>u){let h=Math.ceil(t.length/u);d+=h-1;}let S=d>c;if(s.push({loc:`${m}${f}`,lastmod:P(t),changefreq:a.homePage??"daily",priority:g.homePage??1}),t.forEach(h=>{if(s.length>=c)return;let A=_(h,i.canonical);s.push({loc:A,lastmod:h.updatedAt,changefreq:a.articles??"monthly",priority:g.articles??.8});}),n&&p.length>0&&(s.length<c&&s.push({loc:`${m}${b(f,"topics")}`,lastmod:P(t),changefreq:a.topicPages??"weekly",priority:g.topicPages??.6}),p.forEach(h=>{if(s.length>=c)return;let A=t.filter($=>($.topics??[]).some(E=>E.slug===h.slug));s.push({loc:`${m}${b(f,`topics/${h.slug}`)}`,lastmod:P(A),changefreq:a.topicPages??"weekly",priority:g.topicPages??.6});})),o&&y.length>0&&(s.length<c&&s.push({loc:`${m}${b(f,"authors")}`,lastmod:P(t),changefreq:a.authorPages??"weekly",priority:g.authorPages??.6}),y.forEach(h=>{if(s.length>=c)return;let A=`${m}${v(f,h.slug)}`,$=t.filter(B=>B.author?.id===h.id),E=P($);s.push({loc:A,lastmod:E,changefreq:a.authorPages??"weekly",priority:g.authorPages??.6});})),l&&t.length>u){let h=Math.ceil(t.length/u);for(let A=2;A<=h&&!(s.length>=c);A++)s.push({loc:`${m}${f}?page=${A}`,lastmod:P(t),changefreq:a.paginationPages??"daily",priority:g.paginationPages??.5});}return {entries:s,totalEntries:s.length,truncated:S,generatedAt:R,baseUrl:m}}function C(e){let t='<?xml version="1.0" encoding="UTF-8"?>',r='<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',i="</urlset>",o=e.entries.map(n=>{let l=[];return l.push(" <url>"),l.push(` <loc>${w(n.loc)}</loc>`),n.lastmod&&l.push(` <lastmod>${w(n.lastmod)}</lastmod>`),n.changefreq&&l.push(` <changefreq>${w(n.changefreq)}</changefreq>`),n.priority!==void 0&&l.push(` <priority>${n.priority.toFixed(1)}</priority>`),l.push(" </url>"),l.join(`
2
- `)});return [t,r,...o,i].join(`
3
- `)}function J(e,t){let r='<?xml version="1.0" encoding="UTF-8"?>',i='<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',o="</sitemapindex>",n=e.map(l=>{let u=[];return u.push(" <sitemap>"),u.push(` <loc>${w(l.url)}</loc>`),l.lastmod&&u.push(` <lastmod>${w(l.lastmod)}</lastmod>`),u.push(" </sitemap>"),u.join(`
4
- `)});return [r,i,...n,o].join(`
5
- `)}function Q(e){let t=[],r=[];e.entries.length===0?t.push("Sitemap must contain at least one URL"):e.entries.length>5e4&&t.push("Sitemap contains more than 50,000 URLs (Google limit)"),e.truncated&&r.push("Sitemap was truncated due to size limits"),e.entries.forEach((o,n)=>{try{new URL(o.loc);}catch{t.push(`Invalid URL at entry ${n+1}: ${o.loc}`);}o.priority!==void 0&&(o.priority<0||o.priority>1)&&t.push(`Invalid priority at entry ${n+1}: must be between 0.0 and 1.0`),o.lastmod&&!M(o.lastmod)&&t.push(`Invalid lastmod date at entry ${n+1}: ${o.lastmod}`),o.loc.length>2048&&r.push(`URL at entry ${n+1} exceeds 2048 character limit`);});let i=new Map;return e.entries.forEach(o=>{let n=i.get(o.loc)??0;i.set(o.loc,n+1);}),i.forEach((o,n)=>{o>1&&t.push(`Duplicate URL found: ${n} (appears ${o} times)`);}),{valid:t.length===0,errors:t,warnings:r}}function P(e){return e.length===0?new Date().toISOString():e.reduce((r,i)=>{let o=new Date(i.updatedAt);return o>r?o:r},new Date(e[0].updatedAt)).toISOString()}function Y(e,t,r=[],i={}){return async function(){let l=(await U(e,t,r,i)).map(a=>({loc:a.url,lastmod:typeof a.lastModified=="string"?a.lastModified:a.lastModified?.toISOString(),changefreq:a.changeFrequency,priority:a.priority})),u={entries:l,totalEntries:l.length,generatedAt:new Date().toISOString(),baseUrl:x(i.canonical?.domain??e.domain,i.canonical?.forceHttps??true)},c=C(u);return new Response(c,{headers:{"Content-Type":"application/xml","Cache-Control":"public, max-age=3600, s-maxage=3600"}})}}function K(e){return async function(){let r=[...e.staticRoutes??[],...e.getStaticRoutes?await e.getStaticRoutes():[]],i=await e.getSite(),o=await e.getArticles(),n=e.getAuthors?await e.getAuthors():[];return [...r,...await U(i,o,n,e.options)]}}function w(e){let t={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;"};return e.replace(/[&<>"']/g,r=>t[r]??r)}function M(e){try{let t=new Date(e),r=t.toISOString(),i=e.endsWith("Z")&&!e.includes(".")?e.replace("Z",".000Z"):e;return r===i&&!isNaN(t.getTime())}catch{return false}}exports.createBlogSitemapRoute=Y;exports.createDynamicBlogSitemap=K;exports.generateBlogSitemapEntries=U;exports.generateSitemap=X;exports.generateSitemapIndex=J;exports.sitemapToXml=C;exports.validateSitemap=Q;//# sourceMappingURL=sitemap.js.map
1
+ 'use strict';function T(e){let t=new Map;for(let r of e)r.author?.slug&&t.set(r.author.slug,q(t.get(r.author.slug),r.author));return Array.from(t.values()).sort((r,i)=>r.name.localeCompare(i.name))}function q(e,t){return e?{...e,bio:e.bio??t.bio,avatarUrl:e.avatarUrl??t.avatarUrl,pronouns:e.pronouns??t.pronouns,socialLinks:e.socialLinks??t.socialLinks,toneAdjectives:e.toneAdjectives.length>0?e.toneAdjectives:t.toneAdjectives}:t}function w(e,t=true){let r=e.trim();return r=r.replace(/\/+$/,""),/^https?:\/\//.exec(r)||(r=`${t?"https":"http"}://${r}`),t&&r.startsWith("http://")&&(r=r.replace("http://","https://")),r}function F(e){let t=e.trim();if(!t)throw new Error("Slug cannot be empty");if(!/^[a-z0-9-]+$/.test(t))throw new Error("Slug can only contain lowercase letters, numbers, and hyphens");let i=t.replace(/-+/g,"-").replace(/^-+|-+$/g,"");if(!i)throw new Error("Slug cannot consist only of hyphens");return i}function O(e,t={}){let{forceHttps:r=true,removeTrailingSlash:i=true,domain:s=e.site.domain,pathPrefix:n="/blog"}=t;if(!s)throw new Error("Domain is required for canonical URL generation");if(!e.slug)throw new Error("Article slug is required for canonical URL generation");let l=w(s,r),u=F(e.slug),c=n.replace(/^(?!\/)/,"/").replace(/\/+$/,""),a=`${l}${c}/${u}`;return i&&(a=a.replace(/\/+$/,"")),a}function _(e){let t=new Map;for(let r of e)for(let i of r.topics??[])t.has(i.slug)||t.set(i.slug,i);return Array.from(t.values()).sort((r,i)=>r.name.localeCompare(i.name))}function M(e="/blog"){let t=e.trim();return !t||t==="/"?"":t.replace(/^(?!\/)/,"/").replace(/\/+$/,"")}function b(e,t){let r=M(e),i=t?.replace(/^\/+/,"").replace(/\/+$/,"");return i?`${r}/${i}`.replace(/\/+/g,"/"):r||"/"}function v(e,t){return b(e,`authors/${t}`)}async function U(e,t,r=[],i={}){let{includeAuthorPages:s=true,includeTopicPages:n=true,includePaginationPages:l=true,articlesPerPage:u=10,maxEntries:c=5e4,changeFrequency:a={},priority:g={}}=i,o=[],m=w(i.canonical?.domain??e.domain,i.canonical?.forceHttps??true),f=i.canonical?.pathPrefix??"/blog",R=r.length>0?r:T(t),y=_([...t]);if(o.push({url:`${m}${f}`,lastModified:P(t),changeFrequency:a.homePage??"daily",priority:g.homePage??1}),t.forEach(p=>{if(o.length>=c)return;let d=O(p,i.canonical);o.push({url:d,lastModified:p.updatedAt,changeFrequency:a.articles??"monthly",priority:g.articles??.8});}),n&&y.length>0&&(o.length<c&&o.push({url:`${m}${b(f,"topics")}`,lastModified:P(t),changeFrequency:a.topicPages??"weekly",priority:g.topicPages??.6}),y.forEach(p=>{if(o.length>=c)return;let d=t.filter(S=>(S.topics??[]).some(h=>h.slug===p.slug));o.push({url:`${m}${b(f,`topics/${p.slug}`)}`,lastModified:P(d),changeFrequency:a.topicPages??"weekly",priority:g.topicPages??.6});})),s&&R.length>0&&(o.length<c&&o.push({url:`${m}${b(f,"authors")}`,lastModified:P(t),changeFrequency:a.authorPages??"weekly",priority:g.authorPages??.6}),R.forEach(p=>{if(o.length>=c)return;let d=`${m}${v(f,p.slug)}`,S=t.filter(A=>A.author?.id===p.id),h=P(S);o.push({url:d,lastModified:h,changeFrequency:a.authorPages??"weekly",priority:g.authorPages??.6});})),l&&t.length>u){let p=Math.ceil(t.length/u);for(let d=2;d<=p&&!(o.length>=c);d++)o.push({url:`${m}${f}?page=${d}`,lastModified:P(t),changeFrequency:a.paginationPages??"daily",priority:g.paginationPages??.5});}return o}function X(e,t,r=[],i={}){let{includeAuthorPages:s=true,includeTopicPages:n=true,includePaginationPages:l=true,articlesPerPage:u=10,maxEntries:c=5e4,changeFrequency:a={},priority:g={}}=i,o=[],m=w(i.canonical?.domain??e.domain,i.canonical?.forceHttps??true),f=i.canonical?.pathPrefix??"/blog",R=new Date().toISOString(),y=r.length>0?r:T(t),p=_([...t]),d=1;if(d+=t.length,n&&p.length>0&&(d+=p.length+1),s&&(d+=y.length>0?y.length+1:0),l&&t.length>u){let h=Math.ceil(t.length/u);d+=h-1;}let S=d>c;if(o.push({loc:`${m}${f}`,lastmod:P(t),changefreq:a.homePage??"daily",priority:g.homePage??1}),t.forEach(h=>{if(o.length>=c)return;let A=O(h,i.canonical);o.push({loc:A,lastmod:h.updatedAt,changefreq:a.articles??"monthly",priority:g.articles??.8});}),n&&p.length>0&&(o.length<c&&o.push({loc:`${m}${b(f,"topics")}`,lastmod:P(t),changefreq:a.topicPages??"weekly",priority:g.topicPages??.6}),p.forEach(h=>{if(o.length>=c)return;let A=t.filter($=>($.topics??[]).some(E=>E.slug===h.slug));o.push({loc:`${m}${b(f,`topics/${h.slug}`)}`,lastmod:P(A),changefreq:a.topicPages??"weekly",priority:g.topicPages??.6});})),s&&y.length>0&&(o.length<c&&o.push({loc:`${m}${b(f,"authors")}`,lastmod:P(t),changefreq:a.authorPages??"weekly",priority:g.authorPages??.6}),y.forEach(h=>{if(o.length>=c)return;let A=`${m}${v(f,h.slug)}`,$=t.filter(B=>B.author?.id===h.id),E=P($);o.push({loc:A,lastmod:E,changefreq:a.authorPages??"weekly",priority:g.authorPages??.6});})),l&&t.length>u){let h=Math.ceil(t.length/u);for(let A=2;A<=h&&!(o.length>=c);A++)o.push({loc:`${m}${f}?page=${A}`,lastmod:P(t),changefreq:a.paginationPages??"daily",priority:g.paginationPages??.5});}return {entries:o,totalEntries:o.length,truncated:S,generatedAt:R,baseUrl:m}}function L(e){let t='<?xml version="1.0" encoding="UTF-8"?>',r='<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',i="</urlset>",s=e.entries.map(n=>{let l=[];return l.push(" <url>"),l.push(` <loc>${x(n.loc)}</loc>`),n.lastmod&&l.push(` <lastmod>${x(n.lastmod)}</lastmod>`),n.changefreq&&l.push(` <changefreq>${x(n.changefreq)}</changefreq>`),n.priority!==void 0&&l.push(` <priority>${n.priority.toFixed(1)}</priority>`),l.push(" </url>"),l.join(`
2
+ `)});return [t,r,...s,i].join(`
3
+ `)}function J(e,t){let r='<?xml version="1.0" encoding="UTF-8"?>',i='<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',s="</sitemapindex>",n=e.map(l=>{let u=[];return u.push(" <sitemap>"),u.push(` <loc>${x(l.url)}</loc>`),l.lastmod&&u.push(` <lastmod>${x(l.lastmod)}</lastmod>`),u.push(" </sitemap>"),u.join(`
4
+ `)});return [r,i,...n,s].join(`
5
+ `)}function Q(e){let t=[],r=[];e.entries.length===0?t.push("Sitemap must contain at least one URL"):e.entries.length>5e4&&t.push("Sitemap contains more than 50,000 URLs (Google limit)"),e.truncated&&r.push("Sitemap was truncated due to size limits"),e.entries.forEach((s,n)=>{try{new URL(s.loc);}catch{t.push(`Invalid URL at entry ${n+1}: ${s.loc}`);}s.priority!==void 0&&(s.priority<0||s.priority>1)&&t.push(`Invalid priority at entry ${n+1}: must be between 0.0 and 1.0`),s.lastmod&&!C(s.lastmod)&&t.push(`Invalid lastmod date at entry ${n+1}: ${s.lastmod}`),s.loc.length>2048&&r.push(`URL at entry ${n+1} exceeds 2048 character limit`);});let i=new Map;return e.entries.forEach(s=>{let n=i.get(s.loc)??0;i.set(s.loc,n+1);}),i.forEach((s,n)=>{s>1&&t.push(`Duplicate URL found: ${n} (appears ${s} times)`);}),{valid:t.length===0,errors:t,warnings:r}}function P(e){return e.length===0?new Date().toISOString():e.reduce((r,i)=>{let s=new Date(i.updatedAt);return s>r?s:r},new Date(e[0].updatedAt)).toISOString()}function Y(e,t,r=[],i={}){return async function(){let l=(await U(e,t,r,i)).map(a=>({loc:a.url,lastmod:typeof a.lastModified=="string"?a.lastModified:a.lastModified?.toISOString(),changefreq:a.changeFrequency,priority:a.priority})),u={entries:l,totalEntries:l.length,generatedAt:new Date().toISOString(),baseUrl:w(i.canonical?.domain??e.domain,i.canonical?.forceHttps??true)},c=L(u);return new Response(c,{headers:{"Content-Type":"application/xml","Cache-Control":"public, max-age=3600, s-maxage=3600"}})}}function K(e){return async function(){let r=[...e.staticRoutes??[],...e.getStaticRoutes?await e.getStaticRoutes():[]],i=await e.getSite(),s=await e.getArticles(),n=e.getAuthors?await e.getAuthors():[];return [...r,...await U(i,s,n,e.options)]}}function x(e){let t={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;"};return e.replace(/[&<>"']/g,r=>t[r]??r)}function C(e){try{let t=new Date(e),r=t.toISOString(),i=e.endsWith("Z")&&!e.includes(".")?e.replace("Z",".000Z"):e;return r===i&&!isNaN(t.getTime())}catch{return false}}exports.createBlogSitemapRoute=Y;exports.createDynamicBlogSitemap=K;exports.generateBlogSitemapEntries=U;exports.generateSitemap=X;exports.generateSitemapIndex=J;exports.sitemapToXml=L;exports.validateSitemap=Q;//# sourceMappingURL=sitemap.js.map
6
6
  //# sourceMappingURL=sitemap.js.map
package/dist/sitemap.mjs CHANGED
@@ -1,6 +1,6 @@
1
- function O(e){let t=new Map;for(let r of e)r.author?.slug&&t.set(r.author.slug,q(t.get(r.author.slug),r.author));return Array.from(t.values()).sort((r,i)=>r.name.localeCompare(i.name))}function q(e,t){return e?{...e,bio:e.bio??t.bio,avatarUrl:e.avatarUrl??t.avatarUrl,pronouns:e.pronouns??t.pronouns,socialLinks:e.socialLinks??t.socialLinks,toneAdjectives:e.toneAdjectives.length>0?e.toneAdjectives:t.toneAdjectives}:t}function x(e,t=true){let r=e.trim();return r=r.replace(/\/+$/,""),/^https?:\/\//.exec(r)||(r=`${t?"https":"http"}://${r}`),t&&r.startsWith("http://")&&(r=r.replace("http://","https://")),r}function F(e){let t=e.trim();if(!t)throw new Error("Slug cannot be empty");if(!/^[a-z0-9-]+$/.test(t))throw new Error("Slug can only contain lowercase letters, numbers, and hyphens");let i=t.replace(/-+/g,"-").replace(/^-+|-+$/g,"");if(!i)throw new Error("Slug cannot consist only of hyphens");return i}function _(e,t={}){let{forceHttps:r=true,removeTrailingSlash:i=true,domain:o=e.site.domain,pathPrefix:n="/blog"}=t;if(!o)throw new Error("Domain is required for canonical URL generation");if(!e.slug)throw new Error("Article slug is required for canonical URL generation");let l=x(o,r),u=F(e.slug),c=n.replace(/^(?!\/)/,"/").replace(/\/+$/,""),a=`${l}${c}/${u}`;return i&&(a=a.replace(/\/+$/,"")),a}function T(e){let t=new Map;for(let r of e)for(let i of r.topics??[])t.has(i.slug)||t.set(i.slug,i);return Array.from(t.values()).sort((r,i)=>r.name.localeCompare(i.name))}function L(e="/blog"){let t=e.trim();return !t||t==="/"?"":t.replace(/^(?!\/)/,"/").replace(/\/+$/,"")}function b(e,t){let r=L(e),i=t?.replace(/^\/+/,"").replace(/\/+$/,"");return i?`${r}/${i}`.replace(/\/+/g,"/"):r||"/"}function v(e,t){return b(e,`authors/${t}`)}async function U(e,t,r=[],i={}){let{includeAuthorPages:o=true,includeTopicPages:n=true,includePaginationPages:l=true,articlesPerPage:u=10,maxEntries:c=5e4,changeFrequency:a={},priority:g={}}=i,s=[],m=x(i.canonical?.domain??e.domain,i.canonical?.forceHttps??true),f=i.canonical?.pathPrefix??"/blog",R=r.length>0?r:O(t),y=T([...t]);if(s.push({url:`${m}${f}`,lastModified:P(t),changeFrequency:a.homePage??"daily",priority:g.homePage??1}),t.forEach(p=>{if(s.length>=c)return;let d=_(p,i.canonical);s.push({url:d,lastModified:p.updatedAt,changeFrequency:a.articles??"monthly",priority:g.articles??.8});}),n&&y.length>0&&(s.length<c&&s.push({url:`${m}${b(f,"topics")}`,lastModified:P(t),changeFrequency:a.topicPages??"weekly",priority:g.topicPages??.6}),y.forEach(p=>{if(s.length>=c)return;let d=t.filter(S=>(S.topics??[]).some(h=>h.slug===p.slug));s.push({url:`${m}${b(f,`topics/${p.slug}`)}`,lastModified:P(d),changeFrequency:a.topicPages??"weekly",priority:g.topicPages??.6});})),o&&R.length>0&&(s.length<c&&s.push({url:`${m}${b(f,"authors")}`,lastModified:P(t),changeFrequency:a.authorPages??"weekly",priority:g.authorPages??.6}),R.forEach(p=>{if(s.length>=c)return;let d=`${m}${v(f,p.slug)}`,S=t.filter(A=>A.author?.id===p.id),h=P(S);s.push({url:d,lastModified:h,changeFrequency:a.authorPages??"weekly",priority:g.authorPages??.6});})),l&&t.length>u){let p=Math.ceil(t.length/u);for(let d=2;d<=p&&!(s.length>=c);d++)s.push({url:`${m}${f}?page=${d}`,lastModified:P(t),changeFrequency:a.paginationPages??"daily",priority:g.paginationPages??.5});}return s}function X(e,t,r=[],i={}){let{includeAuthorPages:o=true,includeTopicPages:n=true,includePaginationPages:l=true,articlesPerPage:u=10,maxEntries:c=5e4,changeFrequency:a={},priority:g={}}=i,s=[],m=x(i.canonical?.domain??e.domain,i.canonical?.forceHttps??true),f=i.canonical?.pathPrefix??"/blog",R=new Date().toISOString(),y=r.length>0?r:O(t),p=T([...t]),d=1;if(d+=t.length,n&&p.length>0&&(d+=p.length+1),o&&(d+=y.length>0?y.length+1:0),l&&t.length>u){let h=Math.ceil(t.length/u);d+=h-1;}let S=d>c;if(s.push({loc:`${m}${f}`,lastmod:P(t),changefreq:a.homePage??"daily",priority:g.homePage??1}),t.forEach(h=>{if(s.length>=c)return;let A=_(h,i.canonical);s.push({loc:A,lastmod:h.updatedAt,changefreq:a.articles??"monthly",priority:g.articles??.8});}),n&&p.length>0&&(s.length<c&&s.push({loc:`${m}${b(f,"topics")}`,lastmod:P(t),changefreq:a.topicPages??"weekly",priority:g.topicPages??.6}),p.forEach(h=>{if(s.length>=c)return;let A=t.filter($=>($.topics??[]).some(E=>E.slug===h.slug));s.push({loc:`${m}${b(f,`topics/${h.slug}`)}`,lastmod:P(A),changefreq:a.topicPages??"weekly",priority:g.topicPages??.6});})),o&&y.length>0&&(s.length<c&&s.push({loc:`${m}${b(f,"authors")}`,lastmod:P(t),changefreq:a.authorPages??"weekly",priority:g.authorPages??.6}),y.forEach(h=>{if(s.length>=c)return;let A=`${m}${v(f,h.slug)}`,$=t.filter(B=>B.author?.id===h.id),E=P($);s.push({loc:A,lastmod:E,changefreq:a.authorPages??"weekly",priority:g.authorPages??.6});})),l&&t.length>u){let h=Math.ceil(t.length/u);for(let A=2;A<=h&&!(s.length>=c);A++)s.push({loc:`${m}${f}?page=${A}`,lastmod:P(t),changefreq:a.paginationPages??"daily",priority:g.paginationPages??.5});}return {entries:s,totalEntries:s.length,truncated:S,generatedAt:R,baseUrl:m}}function C(e){let t='<?xml version="1.0" encoding="UTF-8"?>',r='<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',i="</urlset>",o=e.entries.map(n=>{let l=[];return l.push(" <url>"),l.push(` <loc>${w(n.loc)}</loc>`),n.lastmod&&l.push(` <lastmod>${w(n.lastmod)}</lastmod>`),n.changefreq&&l.push(` <changefreq>${w(n.changefreq)}</changefreq>`),n.priority!==void 0&&l.push(` <priority>${n.priority.toFixed(1)}</priority>`),l.push(" </url>"),l.join(`
2
- `)});return [t,r,...o,i].join(`
3
- `)}function J(e,t){let r='<?xml version="1.0" encoding="UTF-8"?>',i='<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',o="</sitemapindex>",n=e.map(l=>{let u=[];return u.push(" <sitemap>"),u.push(` <loc>${w(l.url)}</loc>`),l.lastmod&&u.push(` <lastmod>${w(l.lastmod)}</lastmod>`),u.push(" </sitemap>"),u.join(`
4
- `)});return [r,i,...n,o].join(`
5
- `)}function Q(e){let t=[],r=[];e.entries.length===0?t.push("Sitemap must contain at least one URL"):e.entries.length>5e4&&t.push("Sitemap contains more than 50,000 URLs (Google limit)"),e.truncated&&r.push("Sitemap was truncated due to size limits"),e.entries.forEach((o,n)=>{try{new URL(o.loc);}catch{t.push(`Invalid URL at entry ${n+1}: ${o.loc}`);}o.priority!==void 0&&(o.priority<0||o.priority>1)&&t.push(`Invalid priority at entry ${n+1}: must be between 0.0 and 1.0`),o.lastmod&&!M(o.lastmod)&&t.push(`Invalid lastmod date at entry ${n+1}: ${o.lastmod}`),o.loc.length>2048&&r.push(`URL at entry ${n+1} exceeds 2048 character limit`);});let i=new Map;return e.entries.forEach(o=>{let n=i.get(o.loc)??0;i.set(o.loc,n+1);}),i.forEach((o,n)=>{o>1&&t.push(`Duplicate URL found: ${n} (appears ${o} times)`);}),{valid:t.length===0,errors:t,warnings:r}}function P(e){return e.length===0?new Date().toISOString():e.reduce((r,i)=>{let o=new Date(i.updatedAt);return o>r?o:r},new Date(e[0].updatedAt)).toISOString()}function Y(e,t,r=[],i={}){return async function(){let l=(await U(e,t,r,i)).map(a=>({loc:a.url,lastmod:typeof a.lastModified=="string"?a.lastModified:a.lastModified?.toISOString(),changefreq:a.changeFrequency,priority:a.priority})),u={entries:l,totalEntries:l.length,generatedAt:new Date().toISOString(),baseUrl:x(i.canonical?.domain??e.domain,i.canonical?.forceHttps??true)},c=C(u);return new Response(c,{headers:{"Content-Type":"application/xml","Cache-Control":"public, max-age=3600, s-maxage=3600"}})}}function K(e){return async function(){let r=[...e.staticRoutes??[],...e.getStaticRoutes?await e.getStaticRoutes():[]],i=await e.getSite(),o=await e.getArticles(),n=e.getAuthors?await e.getAuthors():[];return [...r,...await U(i,o,n,e.options)]}}function w(e){let t={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;"};return e.replace(/[&<>"']/g,r=>t[r]??r)}function M(e){try{let t=new Date(e),r=t.toISOString(),i=e.endsWith("Z")&&!e.includes(".")?e.replace("Z",".000Z"):e;return r===i&&!isNaN(t.getTime())}catch{return false}}export{Y as createBlogSitemapRoute,K as createDynamicBlogSitemap,U as generateBlogSitemapEntries,X as generateSitemap,J as generateSitemapIndex,C as sitemapToXml,Q as validateSitemap};//# sourceMappingURL=sitemap.mjs.map
1
+ function T(e){let t=new Map;for(let r of e)r.author?.slug&&t.set(r.author.slug,q(t.get(r.author.slug),r.author));return Array.from(t.values()).sort((r,i)=>r.name.localeCompare(i.name))}function q(e,t){return e?{...e,bio:e.bio??t.bio,avatarUrl:e.avatarUrl??t.avatarUrl,pronouns:e.pronouns??t.pronouns,socialLinks:e.socialLinks??t.socialLinks,toneAdjectives:e.toneAdjectives.length>0?e.toneAdjectives:t.toneAdjectives}:t}function w(e,t=true){let r=e.trim();return r=r.replace(/\/+$/,""),/^https?:\/\//.exec(r)||(r=`${t?"https":"http"}://${r}`),t&&r.startsWith("http://")&&(r=r.replace("http://","https://")),r}function F(e){let t=e.trim();if(!t)throw new Error("Slug cannot be empty");if(!/^[a-z0-9-]+$/.test(t))throw new Error("Slug can only contain lowercase letters, numbers, and hyphens");let i=t.replace(/-+/g,"-").replace(/^-+|-+$/g,"");if(!i)throw new Error("Slug cannot consist only of hyphens");return i}function O(e,t={}){let{forceHttps:r=true,removeTrailingSlash:i=true,domain:s=e.site.domain,pathPrefix:n="/blog"}=t;if(!s)throw new Error("Domain is required for canonical URL generation");if(!e.slug)throw new Error("Article slug is required for canonical URL generation");let l=w(s,r),u=F(e.slug),c=n.replace(/^(?!\/)/,"/").replace(/\/+$/,""),a=`${l}${c}/${u}`;return i&&(a=a.replace(/\/+$/,"")),a}function _(e){let t=new Map;for(let r of e)for(let i of r.topics??[])t.has(i.slug)||t.set(i.slug,i);return Array.from(t.values()).sort((r,i)=>r.name.localeCompare(i.name))}function M(e="/blog"){let t=e.trim();return !t||t==="/"?"":t.replace(/^(?!\/)/,"/").replace(/\/+$/,"")}function b(e,t){let r=M(e),i=t?.replace(/^\/+/,"").replace(/\/+$/,"");return i?`${r}/${i}`.replace(/\/+/g,"/"):r||"/"}function v(e,t){return b(e,`authors/${t}`)}async function U(e,t,r=[],i={}){let{includeAuthorPages:s=true,includeTopicPages:n=true,includePaginationPages:l=true,articlesPerPage:u=10,maxEntries:c=5e4,changeFrequency:a={},priority:g={}}=i,o=[],m=w(i.canonical?.domain??e.domain,i.canonical?.forceHttps??true),f=i.canonical?.pathPrefix??"/blog",R=r.length>0?r:T(t),y=_([...t]);if(o.push({url:`${m}${f}`,lastModified:P(t),changeFrequency:a.homePage??"daily",priority:g.homePage??1}),t.forEach(p=>{if(o.length>=c)return;let d=O(p,i.canonical);o.push({url:d,lastModified:p.updatedAt,changeFrequency:a.articles??"monthly",priority:g.articles??.8});}),n&&y.length>0&&(o.length<c&&o.push({url:`${m}${b(f,"topics")}`,lastModified:P(t),changeFrequency:a.topicPages??"weekly",priority:g.topicPages??.6}),y.forEach(p=>{if(o.length>=c)return;let d=t.filter(S=>(S.topics??[]).some(h=>h.slug===p.slug));o.push({url:`${m}${b(f,`topics/${p.slug}`)}`,lastModified:P(d),changeFrequency:a.topicPages??"weekly",priority:g.topicPages??.6});})),s&&R.length>0&&(o.length<c&&o.push({url:`${m}${b(f,"authors")}`,lastModified:P(t),changeFrequency:a.authorPages??"weekly",priority:g.authorPages??.6}),R.forEach(p=>{if(o.length>=c)return;let d=`${m}${v(f,p.slug)}`,S=t.filter(A=>A.author?.id===p.id),h=P(S);o.push({url:d,lastModified:h,changeFrequency:a.authorPages??"weekly",priority:g.authorPages??.6});})),l&&t.length>u){let p=Math.ceil(t.length/u);for(let d=2;d<=p&&!(o.length>=c);d++)o.push({url:`${m}${f}?page=${d}`,lastModified:P(t),changeFrequency:a.paginationPages??"daily",priority:g.paginationPages??.5});}return o}function X(e,t,r=[],i={}){let{includeAuthorPages:s=true,includeTopicPages:n=true,includePaginationPages:l=true,articlesPerPage:u=10,maxEntries:c=5e4,changeFrequency:a={},priority:g={}}=i,o=[],m=w(i.canonical?.domain??e.domain,i.canonical?.forceHttps??true),f=i.canonical?.pathPrefix??"/blog",R=new Date().toISOString(),y=r.length>0?r:T(t),p=_([...t]),d=1;if(d+=t.length,n&&p.length>0&&(d+=p.length+1),s&&(d+=y.length>0?y.length+1:0),l&&t.length>u){let h=Math.ceil(t.length/u);d+=h-1;}let S=d>c;if(o.push({loc:`${m}${f}`,lastmod:P(t),changefreq:a.homePage??"daily",priority:g.homePage??1}),t.forEach(h=>{if(o.length>=c)return;let A=O(h,i.canonical);o.push({loc:A,lastmod:h.updatedAt,changefreq:a.articles??"monthly",priority:g.articles??.8});}),n&&p.length>0&&(o.length<c&&o.push({loc:`${m}${b(f,"topics")}`,lastmod:P(t),changefreq:a.topicPages??"weekly",priority:g.topicPages??.6}),p.forEach(h=>{if(o.length>=c)return;let A=t.filter($=>($.topics??[]).some(E=>E.slug===h.slug));o.push({loc:`${m}${b(f,`topics/${h.slug}`)}`,lastmod:P(A),changefreq:a.topicPages??"weekly",priority:g.topicPages??.6});})),s&&y.length>0&&(o.length<c&&o.push({loc:`${m}${b(f,"authors")}`,lastmod:P(t),changefreq:a.authorPages??"weekly",priority:g.authorPages??.6}),y.forEach(h=>{if(o.length>=c)return;let A=`${m}${v(f,h.slug)}`,$=t.filter(B=>B.author?.id===h.id),E=P($);o.push({loc:A,lastmod:E,changefreq:a.authorPages??"weekly",priority:g.authorPages??.6});})),l&&t.length>u){let h=Math.ceil(t.length/u);for(let A=2;A<=h&&!(o.length>=c);A++)o.push({loc:`${m}${f}?page=${A}`,lastmod:P(t),changefreq:a.paginationPages??"daily",priority:g.paginationPages??.5});}return {entries:o,totalEntries:o.length,truncated:S,generatedAt:R,baseUrl:m}}function L(e){let t='<?xml version="1.0" encoding="UTF-8"?>',r='<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',i="</urlset>",s=e.entries.map(n=>{let l=[];return l.push(" <url>"),l.push(` <loc>${x(n.loc)}</loc>`),n.lastmod&&l.push(` <lastmod>${x(n.lastmod)}</lastmod>`),n.changefreq&&l.push(` <changefreq>${x(n.changefreq)}</changefreq>`),n.priority!==void 0&&l.push(` <priority>${n.priority.toFixed(1)}</priority>`),l.push(" </url>"),l.join(`
2
+ `)});return [t,r,...s,i].join(`
3
+ `)}function J(e,t){let r='<?xml version="1.0" encoding="UTF-8"?>',i='<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',s="</sitemapindex>",n=e.map(l=>{let u=[];return u.push(" <sitemap>"),u.push(` <loc>${x(l.url)}</loc>`),l.lastmod&&u.push(` <lastmod>${x(l.lastmod)}</lastmod>`),u.push(" </sitemap>"),u.join(`
4
+ `)});return [r,i,...n,s].join(`
5
+ `)}function Q(e){let t=[],r=[];e.entries.length===0?t.push("Sitemap must contain at least one URL"):e.entries.length>5e4&&t.push("Sitemap contains more than 50,000 URLs (Google limit)"),e.truncated&&r.push("Sitemap was truncated due to size limits"),e.entries.forEach((s,n)=>{try{new URL(s.loc);}catch{t.push(`Invalid URL at entry ${n+1}: ${s.loc}`);}s.priority!==void 0&&(s.priority<0||s.priority>1)&&t.push(`Invalid priority at entry ${n+1}: must be between 0.0 and 1.0`),s.lastmod&&!C(s.lastmod)&&t.push(`Invalid lastmod date at entry ${n+1}: ${s.lastmod}`),s.loc.length>2048&&r.push(`URL at entry ${n+1} exceeds 2048 character limit`);});let i=new Map;return e.entries.forEach(s=>{let n=i.get(s.loc)??0;i.set(s.loc,n+1);}),i.forEach((s,n)=>{s>1&&t.push(`Duplicate URL found: ${n} (appears ${s} times)`);}),{valid:t.length===0,errors:t,warnings:r}}function P(e){return e.length===0?new Date().toISOString():e.reduce((r,i)=>{let s=new Date(i.updatedAt);return s>r?s:r},new Date(e[0].updatedAt)).toISOString()}function Y(e,t,r=[],i={}){return async function(){let l=(await U(e,t,r,i)).map(a=>({loc:a.url,lastmod:typeof a.lastModified=="string"?a.lastModified:a.lastModified?.toISOString(),changefreq:a.changeFrequency,priority:a.priority})),u={entries:l,totalEntries:l.length,generatedAt:new Date().toISOString(),baseUrl:w(i.canonical?.domain??e.domain,i.canonical?.forceHttps??true)},c=L(u);return new Response(c,{headers:{"Content-Type":"application/xml","Cache-Control":"public, max-age=3600, s-maxage=3600"}})}}function K(e){return async function(){let r=[...e.staticRoutes??[],...e.getStaticRoutes?await e.getStaticRoutes():[]],i=await e.getSite(),s=await e.getArticles(),n=e.getAuthors?await e.getAuthors():[];return [...r,...await U(i,s,n,e.options)]}}function x(e){let t={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;"};return e.replace(/[&<>"']/g,r=>t[r]??r)}function C(e){try{let t=new Date(e),r=t.toISOString(),i=e.endsWith("Z")&&!e.includes(".")?e.replace("Z",".000Z"):e;return r===i&&!isNaN(t.getTime())}catch{return false}}export{Y as createBlogSitemapRoute,K as createDynamicBlogSitemap,U as generateBlogSitemapEntries,X as generateSitemap,J as generateSitemapIndex,L as sitemapToXml,Q as validateSitemap};//# sourceMappingURL=sitemap.mjs.map
6
6
  //# sourceMappingURL=sitemap.mjs.map
@@ -323,3 +323,24 @@ export interface UnpublishPayload {
323
323
  /** Whether this is a dry run (validation only) */
324
324
  readonly dryRun?: boolean;
325
325
  }
326
+
327
+ /**
328
+ * Search-engine submission settings synced from Bloggy core to the downstream
329
+ * blog platform.
330
+ */
331
+ export interface SearchSubmissionConfig {
332
+ readonly indexNow?: {
333
+ readonly enabled: boolean;
334
+ readonly key: string;
335
+ readonly keyLocation: string;
336
+ };
337
+ }
338
+
339
+ /**
340
+ * Versioned platform configuration payload accepted by the blog platform
341
+ * configuration endpoint.
342
+ */
343
+ export interface BlogPlatformConfigPayload {
344
+ readonly version: 1;
345
+ readonly searchSubmission?: SearchSubmissionConfig;
346
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xexr/blog-platform",
3
- "version": "1.0.17",
3
+ "version": "1.1.1",
4
4
  "description": "SEO-friendly blog platform for Next.js 15 with Bloggy integration",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",