@xexr/blog-platform 1.0.16 → 1.1.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.
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 & {
package/dist/lib/mdx.ts CHANGED
@@ -1,3 +1,9 @@
1
+ import type { MDXRemoteOptions } from "next-mdx-remote-client/rsc";
2
+ import rehypeRaw from "rehype-raw";
3
+ import rehypeSanitize, {
4
+ defaultSchema,
5
+ type Options as RehypeSanitizeOptions,
6
+ } from "rehype-sanitize";
1
7
  import remarkGfm from "remark-gfm";
2
8
  import remarkToc from "remark-toc";
3
9
  import rehypeSlug from "rehype-slug";
@@ -9,10 +15,48 @@ interface HastNode {
9
15
  children?: HastNode[];
10
16
  }
11
17
 
12
- export const articleMdxOptions = {
18
+ const globalAttributes = defaultSchema.attributes?.["*"] ?? [];
19
+ const anchorAttributes = defaultSchema.attributes?.a ?? [];
20
+ const imageAttributes = defaultSchema.attributes?.img ?? [];
21
+
22
+ export const articleMdxSanitizeSchema: RehypeSanitizeOptions = {
23
+ ...defaultSchema,
24
+ tagNames: [...(defaultSchema.tagNames ?? []), "figure", "figcaption"],
25
+ attributes: {
26
+ ...defaultSchema.attributes,
27
+ "*": [...globalAttributes, "className"],
28
+ a: [...anchorAttributes, "className", "href", "target", "rel", "title"],
29
+ img: [
30
+ ...imageAttributes,
31
+ "className",
32
+ "src",
33
+ "alt",
34
+ "title",
35
+ "width",
36
+ "height",
37
+ "loading",
38
+ ],
39
+ figure: ["className"],
40
+ figcaption: ["className"],
41
+ span: ["className"],
42
+ },
43
+ protocols: {
44
+ ...defaultSchema.protocols,
45
+ href: ["http", "https", "mailto", "tel"],
46
+ src: ["http", "https"],
47
+ },
48
+ };
49
+
50
+ export const articleMdxOptions: MDXRemoteOptions = {
13
51
  mdxOptions: {
52
+ format: "md",
14
53
  remarkPlugins: [remarkToc, remarkGfm],
15
- rehypePlugins: [rehypeUnwrapStandaloneImages, rehypeSlug],
54
+ rehypePlugins: [
55
+ rehypeRaw,
56
+ [rehypeSanitize, articleMdxSanitizeSchema],
57
+ rehypeUnwrapStandaloneImages,
58
+ rehypeSlug,
59
+ ],
16
60
  },
17
61
  };
18
62
 
@@ -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 {
@@ -128,6 +150,38 @@ export interface DrizzleStorageOptions {
128
150
  operationTtlMs?: number;
129
151
  }
130
152
 
153
+ export interface BlogPlatformSchemaCheckResult {
154
+ ok: boolean;
155
+ schemaVersion: number;
156
+ requiredTables: readonly BlogPlatformTableName[];
157
+ presentTables: BlogPlatformTableName[];
158
+ missingTables: BlogPlatformTableName[];
159
+ setupSql: string[];
160
+ }
161
+
162
+ export class BlogPlatformSchemaError extends Error {
163
+ readonly code = "BLOG_PLATFORM_SCHEMA_MISSING";
164
+ readonly missingTables: BlogPlatformTableName[];
165
+ readonly setupSql: string[];
166
+
167
+ constructor(
168
+ missingTables: BlogPlatformTableName[],
169
+ options: { cause?: unknown } = {},
170
+ ) {
171
+ super(
172
+ `Blog platform database schema is missing required table(s): ${missingTables.join(
173
+ ", ",
174
+ )}. Run the @xexr/blog-platform schema migration/setup for the current package version.`,
175
+ );
176
+ this.name = "BlogPlatformSchemaError";
177
+ this.missingTables = missingTables;
178
+ this.setupSql = missingTables.map(getCreateTableSqlForName);
179
+ if (options.cause !== undefined) {
180
+ this.cause = options.cause;
181
+ }
182
+ }
183
+ }
184
+
131
185
  /**
132
186
  * Drizzle-backed durable storage adapter.
133
187
  * Pass a Drizzle Database instance created with drizzle-orm/libsql.
@@ -374,18 +428,81 @@ export class DrizzleStorage implements BlogPlatformStorage {
374
428
  })
375
429
  .run();
376
430
  }
431
+
432
+ async setMetadata(key: string, value: unknown): Promise<void> {
433
+ const now = new Date();
434
+ try {
435
+ await this.db.delete(bpMetadata).where(eq(bpMetadata.key, key)).run();
436
+ await this.db
437
+ .insert(bpMetadata)
438
+ .values({
439
+ key,
440
+ valueJson: JSON.stringify(value),
441
+ updatedAt: now,
442
+ })
443
+ .run();
444
+ } catch (error) {
445
+ throwSchemaErrorForMissingTable(error, "bp_metadata");
446
+ }
447
+ }
448
+
449
+ async getMetadata<T = unknown>(key: string): Promise<T | null> {
450
+ let rows: { valueJson: string }[];
451
+ try {
452
+ rows = await this.db
453
+ .select({ valueJson: bpMetadata.valueJson })
454
+ .from(bpMetadata)
455
+ .where(eq(bpMetadata.key, key))
456
+ .limit(1);
457
+ } catch (error) {
458
+ throwSchemaErrorForMissingTable(error, "bp_metadata");
459
+ }
460
+ const row = rows[0];
461
+ if (!row) return null;
462
+ try {
463
+ return JSON.parse(row.valueJson) as T;
464
+ } catch {
465
+ return null;
466
+ }
467
+ }
377
468
  }
378
469
 
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
- } {
470
+ export async function checkDrizzleAdapterSchema(
471
+ db: LibSQLDatabase,
472
+ ): Promise<BlogPlatformSchemaCheckResult> {
473
+ const rows = await db.all<{ name: string }>(sql`
474
+ SELECT name
475
+ FROM sqlite_master
476
+ WHERE type = 'table'
477
+ `);
478
+ const presentTableNames = new Set(rows.map((row) => row.name));
479
+ const presentTables = BLOG_PLATFORM_TABLE_NAMES.filter((tableName) =>
480
+ presentTableNames.has(tableName),
481
+ );
482
+ const missingTables = BLOG_PLATFORM_TABLE_NAMES.filter(
483
+ (tableName) => !presentTableNames.has(tableName),
484
+ );
485
+
387
486
  return {
388
- createArticles: `CREATE TABLE IF NOT EXISTS bp_articles (
487
+ ok: missingTables.length === 0,
488
+ schemaVersion: BLOG_PLATFORM_SCHEMA_VERSION,
489
+ requiredTables: BLOG_PLATFORM_TABLE_NAMES,
490
+ presentTables,
491
+ missingTables,
492
+ setupSql: missingTables.map(getCreateTableSqlForName),
493
+ };
494
+ }
495
+
496
+ export async function ensureDrizzleAdapterSchema(
497
+ db: LibSQLDatabase,
498
+ ): Promise<void> {
499
+ const result = await checkDrizzleAdapterSchema(db);
500
+ if (!result.ok) {
501
+ throw new BlogPlatformSchemaError(result.missingTables);
502
+ }
503
+ }
504
+
505
+ const CREATE_ARTICLES_TABLE_SQL = `CREATE TABLE IF NOT EXISTS bp_articles (
389
506
  id TEXT PRIMARY KEY,
390
507
  slug TEXT NOT NULL UNIQUE,
391
508
  title TEXT NOT NULL,
@@ -404,12 +521,93 @@ export function getDrizzleAdapterSql(): {
404
521
  site_json TEXT NOT NULL,
405
522
  meta_json TEXT NOT NULL,
406
523
  version INTEGER NOT NULL
407
- );`,
408
- createOperations: `CREATE TABLE IF NOT EXISTS bp_operations (
524
+ );`;
525
+
526
+ const CREATE_OPERATIONS_TABLE_SQL = `CREATE TABLE IF NOT EXISTS bp_operations (
409
527
  key TEXT PRIMARY KEY,
410
528
  result_json TEXT NOT NULL,
411
529
  created_at INTEGER NOT NULL,
412
530
  expires_at INTEGER
413
- );`,
531
+ );`;
532
+
533
+ const CREATE_METADATA_TABLE_SQL = `CREATE TABLE IF NOT EXISTS bp_metadata (
534
+ key TEXT PRIMARY KEY,
535
+ value_json TEXT NOT NULL,
536
+ updated_at INTEGER NOT NULL
537
+ );`;
538
+
539
+ function getCreateTableSqlForName(tableName: BlogPlatformTableName): string {
540
+ switch (tableName) {
541
+ case "bp_articles":
542
+ return CREATE_ARTICLES_TABLE_SQL;
543
+ case "bp_operations":
544
+ return CREATE_OPERATIONS_TABLE_SQL;
545
+ case "bp_metadata":
546
+ return CREATE_METADATA_TABLE_SQL;
547
+ }
548
+ }
549
+
550
+ function throwSchemaErrorForMissingTable(
551
+ error: unknown,
552
+ tableName: BlogPlatformTableName,
553
+ ): never {
554
+ if (isMissingTableError(error, tableName)) {
555
+ throw new BlogPlatformSchemaError([tableName], { cause: error });
556
+ }
557
+ throw error;
558
+ }
559
+
560
+ function isMissingTableError(
561
+ error: unknown,
562
+ tableName: BlogPlatformTableName,
563
+ ): boolean {
564
+ return getErrorMessages(error).some(
565
+ (message) =>
566
+ message.includes(`no such table: ${tableName}`) ||
567
+ message.includes(`no such table: main.${tableName}`),
568
+ );
569
+ }
570
+
571
+ function getErrorMessages(error: unknown): string[] {
572
+ if (!error || typeof error !== "object") {
573
+ return [String(error)];
574
+ }
575
+
576
+ const messages: string[] = [];
577
+ if (error instanceof Error) {
578
+ messages.push(error.message);
579
+ }
580
+
581
+ const cause = (error as { cause?: unknown }).cause;
582
+ if (cause !== undefined) {
583
+ messages.push(...getErrorMessages(cause));
584
+ }
585
+
586
+ return messages;
587
+ }
588
+
589
+ /**
590
+ * Helper: Return SQL strings for creating required tables.
591
+ * Consumers can embed these in their migrations.
592
+ */
593
+ export function getDrizzleAdapterSql(): {
594
+ schemaVersion: number;
595
+ requiredTableNames: readonly BlogPlatformTableName[];
596
+ createAll: string[];
597
+ createArticles: string;
598
+ createOperations: string;
599
+ createMetadata: string;
600
+ } {
601
+ return {
602
+ schemaVersion: BLOG_PLATFORM_SCHEMA_VERSION,
603
+ requiredTableNames: BLOG_PLATFORM_TABLE_NAMES,
604
+ createAll: [
605
+ CREATE_ARTICLES_TABLE_SQL,
606
+ CREATE_OPERATIONS_TABLE_SQL,
607
+ CREATE_METADATA_TABLE_SQL,
608
+ ],
609
+ createArticles: CREATE_ARTICLES_TABLE_SQL,
610
+ createOperations: CREATE_OPERATIONS_TABLE_SQL,
611
+ createMetadata: CREATE_METADATA_TABLE_SQL,
414
612
  };
415
613
  }
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.16",
3
+ "version": "1.1.0",
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",
@@ -70,6 +70,8 @@
70
70
  "lucide-react": "^1.14.0",
71
71
  "next-mdx-remote-client": "^2.1.10",
72
72
  "react-icons": "^5.6.0",
73
+ "rehype-raw": "^7.0.0",
74
+ "rehype-sanitize": "^6.0.0",
73
75
  "rehype-slug": "^6.0.0",
74
76
  "remark-gfm": "^4.0.1",
75
77
  "remark-toc": "^9.0.0",