@xexr/blog-platform 1.0.17 → 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/CHANGELOG.md +6 -0
- package/README.md +94 -5
- package/dist/app/api/blog/config/route.ts +259 -0
- package/dist/app/api/blog/publish/route.ts +1 -1
- package/dist/app/api/blog/slugs/[slug]/route.ts +1 -1
- package/dist/app/api/blog/unpublish/route.ts +1 -1
- package/dist/app/api/blog/version/route.ts +1 -1
- package/dist/app/blog/[slug]/page.tsx +2 -2
- package/dist/app/blog/authors/[slug]/page.tsx +2 -2
- package/dist/app/blog/page.tsx +2 -2
- package/dist/app/bloggy-indexnow-key.txt/route.ts +22 -0
- package/dist/components/AuthorByline.tsx +1 -1
- package/dist/components/AuthorPages.tsx +2 -2
- package/dist/components/TopicArchivePages.tsx +2 -2
- package/dist/index.d.mts +605 -5
- package/dist/index.d.ts +605 -5
- package/dist/index.js +20 -12
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +20 -12
- package/dist/index.mjs.map +1 -1
- package/dist/lib/auth.ts +1 -1
- package/dist/lib/cors.ts +2 -2
- package/dist/lib/database.ts +20 -0
- package/dist/lib/security-headers.ts +1 -1
- package/dist/lib/storage/drizzle.ts +210 -12
- package/dist/sitemap.js +5 -5
- package/dist/sitemap.mjs +5 -5
- package/dist/types/contracts.ts +21 -0
- package/package.json +1 -1
package/dist/lib/auth.ts
CHANGED
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
|
/**
|
package/dist/lib/database.ts
CHANGED
|
@@ -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 & {
|
|
@@ -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
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2
|
-
`)});return [t,r,...
|
|
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">',
|
|
4
|
-
`)});return [r,i,...n,
|
|
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((
|
|
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={"&":"&","<":"<",">":">",'"':""","'":"'"};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
|
|
2
|
-
`)});return [t,r,...
|
|
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">',
|
|
4
|
-
`)});return [r,i,...n,
|
|
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((
|
|
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={"&":"&","<":"<",">":">",'"':""","'":"'"};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
|
package/dist/types/contracts.ts
CHANGED
|
@@ -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
|
+
}
|