@autoblogwriter/sdk 1.0.2

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.
@@ -0,0 +1,69 @@
1
+ import { B as BlogAutoClientConfig, F as FetchRequestOptions, P as PostsResponse, a as BlogPost, b as BuildSitemapOptions, M as MetadataRouteSitemap, c as BuildRobotsOptions, d as MetadataRouteRobots, A as AuthMode, I as InternalClientConfig } from './revalidate-445OJMx_.cjs';
2
+ export { g as BlogAutoRevalidatePayload, i as FetchNextConfig, f as PaginatedList, R as RevalidatePathFn, h as RevalidateTagFn, S as SitemapEntry, e as createRevalidateRouteHandler, v as verifyWebhookSignature } from './revalidate-445OJMx_.cjs';
3
+
4
+ interface BlogAutoClient {
5
+ getPosts(params?: {
6
+ limit?: number;
7
+ cursor?: string;
8
+ } & FetchRequestOptions): Promise<PostsResponse>;
9
+ getPostBySlug(slug: string, options?: FetchRequestOptions): Promise<BlogPost | null>;
10
+ getSitemapEntries(): Promise<Array<{
11
+ slug: string;
12
+ updatedAt: string;
13
+ }>>;
14
+ }
15
+ declare function createBlogAutoClient(config: BlogAutoClientConfig): BlogAutoClient;
16
+
17
+ declare function buildSitemap(opts: BuildSitemapOptions): MetadataRouteSitemap;
18
+
19
+ declare function buildRobots(opts: BuildRobotsOptions): MetadataRouteRobots;
20
+
21
+ declare function renderMarkdownToHtml(markdown: string): string;
22
+
23
+ interface NextMetadata {
24
+ title?: string;
25
+ description?: string;
26
+ alternates?: {
27
+ canonical?: string;
28
+ };
29
+ openGraph?: {
30
+ title?: string;
31
+ description?: string;
32
+ images?: Array<{
33
+ url: string;
34
+ }>;
35
+ };
36
+ }
37
+ declare function buildNextMetadata(post: BlogPost): NextMetadata;
38
+
39
+ declare function buildAuthHeaders(apiKey: string, authMode?: AuthMode): Record<string, string>;
40
+
41
+ declare const DEFAULT_TIMEOUT_MS = 10000;
42
+ declare function resolveClientConfig(config: BlogAutoClientConfig): InternalClientConfig;
43
+
44
+ declare class BlogAutoError extends Error {
45
+ readonly causeError?: unknown;
46
+ constructor(message: string, options?: {
47
+ cause?: unknown;
48
+ });
49
+ }
50
+ declare class ConfigError extends BlogAutoError {
51
+ }
52
+ interface ApiErrorDetails {
53
+ status: number;
54
+ code?: string;
55
+ details?: unknown;
56
+ }
57
+ declare class ApiError extends BlogAutoError {
58
+ readonly status: number;
59
+ readonly code?: string;
60
+ readonly details?: unknown;
61
+ constructor(message: string, info: ApiErrorDetails, options?: {
62
+ cause?: unknown;
63
+ });
64
+ }
65
+ declare class NotFoundError extends ApiError {
66
+ constructor(message: string, info?: Partial<ApiErrorDetails>);
67
+ }
68
+
69
+ export { ApiError, type BlogAutoClient, BlogAutoClientConfig, BlogAutoError, BlogPost, BuildRobotsOptions, BuildSitemapOptions, ConfigError, DEFAULT_TIMEOUT_MS, FetchRequestOptions, MetadataRouteRobots, MetadataRouteSitemap, type NextMetadata, NotFoundError, PostsResponse, buildAuthHeaders, buildNextMetadata, buildRobots, buildSitemap, createBlogAutoClient, renderMarkdownToHtml, resolveClientConfig };
@@ -0,0 +1,69 @@
1
+ import { B as BlogAutoClientConfig, F as FetchRequestOptions, P as PostsResponse, a as BlogPost, b as BuildSitemapOptions, M as MetadataRouteSitemap, c as BuildRobotsOptions, d as MetadataRouteRobots, A as AuthMode, I as InternalClientConfig } from './revalidate-445OJMx_.js';
2
+ export { g as BlogAutoRevalidatePayload, i as FetchNextConfig, f as PaginatedList, R as RevalidatePathFn, h as RevalidateTagFn, S as SitemapEntry, e as createRevalidateRouteHandler, v as verifyWebhookSignature } from './revalidate-445OJMx_.js';
3
+
4
+ interface BlogAutoClient {
5
+ getPosts(params?: {
6
+ limit?: number;
7
+ cursor?: string;
8
+ } & FetchRequestOptions): Promise<PostsResponse>;
9
+ getPostBySlug(slug: string, options?: FetchRequestOptions): Promise<BlogPost | null>;
10
+ getSitemapEntries(): Promise<Array<{
11
+ slug: string;
12
+ updatedAt: string;
13
+ }>>;
14
+ }
15
+ declare function createBlogAutoClient(config: BlogAutoClientConfig): BlogAutoClient;
16
+
17
+ declare function buildSitemap(opts: BuildSitemapOptions): MetadataRouteSitemap;
18
+
19
+ declare function buildRobots(opts: BuildRobotsOptions): MetadataRouteRobots;
20
+
21
+ declare function renderMarkdownToHtml(markdown: string): string;
22
+
23
+ interface NextMetadata {
24
+ title?: string;
25
+ description?: string;
26
+ alternates?: {
27
+ canonical?: string;
28
+ };
29
+ openGraph?: {
30
+ title?: string;
31
+ description?: string;
32
+ images?: Array<{
33
+ url: string;
34
+ }>;
35
+ };
36
+ }
37
+ declare function buildNextMetadata(post: BlogPost): NextMetadata;
38
+
39
+ declare function buildAuthHeaders(apiKey: string, authMode?: AuthMode): Record<string, string>;
40
+
41
+ declare const DEFAULT_TIMEOUT_MS = 10000;
42
+ declare function resolveClientConfig(config: BlogAutoClientConfig): InternalClientConfig;
43
+
44
+ declare class BlogAutoError extends Error {
45
+ readonly causeError?: unknown;
46
+ constructor(message: string, options?: {
47
+ cause?: unknown;
48
+ });
49
+ }
50
+ declare class ConfigError extends BlogAutoError {
51
+ }
52
+ interface ApiErrorDetails {
53
+ status: number;
54
+ code?: string;
55
+ details?: unknown;
56
+ }
57
+ declare class ApiError extends BlogAutoError {
58
+ readonly status: number;
59
+ readonly code?: string;
60
+ readonly details?: unknown;
61
+ constructor(message: string, info: ApiErrorDetails, options?: {
62
+ cause?: unknown;
63
+ });
64
+ }
65
+ declare class NotFoundError extends ApiError {
66
+ constructor(message: string, info?: Partial<ApiErrorDetails>);
67
+ }
68
+
69
+ export { ApiError, type BlogAutoClient, BlogAutoClientConfig, BlogAutoError, BlogPost, BuildRobotsOptions, BuildSitemapOptions, ConfigError, DEFAULT_TIMEOUT_MS, FetchRequestOptions, MetadataRouteRobots, MetadataRouteSitemap, type NextMetadata, NotFoundError, PostsResponse, buildAuthHeaders, buildNextMetadata, buildRobots, buildSitemap, createBlogAutoClient, renderMarkdownToHtml, resolveClientConfig };
package/dist/index.js ADDED
@@ -0,0 +1,542 @@
1
+ // src/auth.ts
2
+ function buildAuthHeaders(apiKey, authMode = "bearer") {
3
+ if (authMode === "x-api-key") {
4
+ return { "x-api-key": apiKey };
5
+ }
6
+ return { Authorization: `Bearer ${apiKey}` };
7
+ }
8
+
9
+ // src/errors.ts
10
+ var BlogAutoError = class extends Error {
11
+ constructor(message, options) {
12
+ super(message);
13
+ this.name = new.target.name;
14
+ this.causeError = options?.cause;
15
+ }
16
+ };
17
+ var ConfigError = class extends BlogAutoError {
18
+ };
19
+ var ApiError = class extends BlogAutoError {
20
+ constructor(message, info, options) {
21
+ super(message, options);
22
+ this.status = info.status;
23
+ this.code = info.code;
24
+ this.details = info.details;
25
+ }
26
+ };
27
+ var NotFoundError = class extends ApiError {
28
+ constructor(message, info) {
29
+ super(message, { status: info?.status ?? 404, code: info?.code, details: info?.details });
30
+ }
31
+ };
32
+
33
+ // src/utils.ts
34
+ var DEFAULT_TIMEOUT_MS = 1e4;
35
+ function normalizeApiUrl(apiUrl) {
36
+ if (!apiUrl) {
37
+ throw new ConfigError("apiUrl is required");
38
+ }
39
+ return apiUrl.replace(/\/$/, "");
40
+ }
41
+ function resolveClientConfig(config) {
42
+ if (!config) {
43
+ throw new ConfigError("Client configuration is required");
44
+ }
45
+ const apiKey = config.apiKey?.trim();
46
+ if (!apiKey) {
47
+ throw new ConfigError("apiKey is required to authenticate with BlogAuto");
48
+ }
49
+ if (!config.workspaceId && !config.workspaceSlug) {
50
+ throw new ConfigError("Provide either workspaceId or workspaceSlug");
51
+ }
52
+ const authMode = config.authMode ?? "bearer";
53
+ return {
54
+ apiKey,
55
+ apiUrl: normalizeApiUrl(config.apiUrl),
56
+ workspaceId: config.workspaceId,
57
+ workspaceSlug: config.workspaceSlug,
58
+ authMode,
59
+ fetch: config.fetch ?? globalThis.fetch.bind(globalThis),
60
+ headers: { ...config.headers ?? {} },
61
+ timeoutMs: config.timeoutMs ?? DEFAULT_TIMEOUT_MS
62
+ };
63
+ }
64
+ function buildQuery(params) {
65
+ const query = new URLSearchParams();
66
+ Object.entries(params).forEach(([key, value]) => {
67
+ if (value === void 0 || value === null || value === "") {
68
+ return;
69
+ }
70
+ query.set(key, String(value));
71
+ });
72
+ const qs = query.toString();
73
+ return qs ? `?${qs}` : "";
74
+ }
75
+ async function withTimeout(promise, timeoutMs) {
76
+ if (!timeoutMs) {
77
+ return promise;
78
+ }
79
+ let timeoutId;
80
+ const timeoutPromise = new Promise((_, reject) => {
81
+ timeoutId = setTimeout(() => {
82
+ reject(
83
+ new ApiError(`Request timed out after ${timeoutMs}ms`, {
84
+ status: 408
85
+ })
86
+ );
87
+ }, timeoutMs);
88
+ });
89
+ try {
90
+ return await Promise.race([promise, timeoutPromise]);
91
+ } finally {
92
+ clearTimeout(timeoutId);
93
+ }
94
+ }
95
+ function mergeHeaders(...headerObjects) {
96
+ return headerObjects.reduce((acc, headers) => {
97
+ if (!headers) {
98
+ return acc;
99
+ }
100
+ Object.entries(headers).forEach(([key, value]) => {
101
+ if (value === void 0 || value === null) {
102
+ return;
103
+ }
104
+ acc[key.toLowerCase()] = value;
105
+ });
106
+ return acc;
107
+ }, {});
108
+ }
109
+
110
+ // src/client.ts
111
+ function createBlogAutoClient(config) {
112
+ const resolved = resolveClientConfig(config);
113
+ const fetchImpl = resolved.fetch;
114
+ function ensureWorkspaceSlug() {
115
+ if (!resolved.workspaceSlug) {
116
+ throw new ConfigError("workspaceSlug is required to call the BlogAuto public API");
117
+ }
118
+ return resolved.workspaceSlug;
119
+ }
120
+ function buildUrl(path, query) {
121
+ const qs = buildQuery(query ?? {});
122
+ return `${resolved.apiUrl}${path}${qs}`;
123
+ }
124
+ function unwrapSuccessPayload(payload) {
125
+ if (payload && typeof payload === "object") {
126
+ const maybePayload = payload;
127
+ if ("success" in maybePayload) {
128
+ if (maybePayload.success === false) {
129
+ throw new ApiError(
130
+ maybePayload.error?.message ?? "BlogAuto request failed",
131
+ { status: 500, details: maybePayload }
132
+ );
133
+ }
134
+ if (maybePayload.success && "data" in maybePayload) {
135
+ return maybePayload.data;
136
+ }
137
+ }
138
+ }
139
+ return payload;
140
+ }
141
+ async function request(opts) {
142
+ const method = opts.method ?? "GET";
143
+ const url = buildUrl(opts.path, opts.query);
144
+ const authHeaders = buildAuthHeaders(resolved.apiKey, resolved.authMode);
145
+ const baseHeaders = mergeHeaders(resolved.headers, authHeaders, opts.headers);
146
+ const init = {
147
+ method,
148
+ headers: baseHeaders
149
+ };
150
+ if (opts.body) {
151
+ init.body = typeof opts.body === "string" ? opts.body : JSON.stringify(opts.body);
152
+ init.headers = {
153
+ ...baseHeaders,
154
+ "content-type": baseHeaders["content-type"] ?? "application/json"
155
+ };
156
+ }
157
+ if (opts.cache !== void 0) {
158
+ init.cache = opts.cache;
159
+ }
160
+ if (opts.next) {
161
+ init.next = opts.next;
162
+ }
163
+ try {
164
+ const response = await withTimeout(fetchImpl(url, init), resolved.timeoutMs);
165
+ if (opts.allowNotFound && response.status === 404) {
166
+ return null;
167
+ }
168
+ const contentType = response.headers.get("content-type") ?? "";
169
+ const isJson = contentType.includes("application/json");
170
+ const payload = isJson ? await response.json().catch(() => void 0) : await response.text();
171
+ if (!response.ok) {
172
+ if (response.status === 404 && opts.allowNotFound) {
173
+ return null;
174
+ }
175
+ const errorBody = payload;
176
+ const message = errorBody?.error?.message ?? errorBody?.message ?? `Request failed with status ${response.status}`;
177
+ throw new ApiError(message, {
178
+ status: response.status,
179
+ code: errorBody?.error?.code ?? errorBody?.code,
180
+ details: errorBody?.error?.details ?? errorBody
181
+ });
182
+ }
183
+ if (isJson) {
184
+ return unwrapSuccessPayload(payload);
185
+ }
186
+ return payload ?? void 0;
187
+ } catch (error) {
188
+ if (error instanceof BlogAutoError) {
189
+ throw error;
190
+ }
191
+ throw new ApiError("Network request to BlogAuto failed", { status: 0 }, { cause: error });
192
+ }
193
+ }
194
+ async function fetchBlogPage(limit, page, cacheOptions) {
195
+ const workspaceSlug = ensureWorkspaceSlug();
196
+ return request({
197
+ path: `/v1/public/${encodeURIComponent(workspaceSlug)}/blogs`,
198
+ query: { limit, page },
199
+ cache: cacheOptions?.cache,
200
+ next: cacheOptions?.next
201
+ });
202
+ }
203
+ return {
204
+ async getPosts(params) {
205
+ const limit = params?.limit ?? 20;
206
+ const cursorPage = params?.cursor ? Number(params.cursor) : void 0;
207
+ const page = Number.isFinite(cursorPage) && cursorPage >= 1 ? cursorPage : 1;
208
+ const data = await fetchBlogPage(limit, page, params);
209
+ return {
210
+ posts: data.items,
211
+ nextCursor: data.pagination.hasMore ? String(data.pagination.page + 1) : void 0
212
+ };
213
+ },
214
+ async getPostBySlug(slug, options) {
215
+ if (!slug) {
216
+ throw new ApiError("slug is required", { status: 400 });
217
+ }
218
+ const workspaceSlug = ensureWorkspaceSlug();
219
+ const post = await request({
220
+ path: `/v1/public/${encodeURIComponent(workspaceSlug)}/blogs/${encodeURIComponent(slug)}`,
221
+ allowNotFound: true,
222
+ cache: options?.cache,
223
+ next: options?.next
224
+ });
225
+ if (post === null) {
226
+ return null;
227
+ }
228
+ return post.post;
229
+ },
230
+ async getSitemapEntries() {
231
+ const entries = [];
232
+ const limit = 100;
233
+ let page = 1;
234
+ while (true) {
235
+ const pageData = await fetchBlogPage(limit, page);
236
+ pageData.items.forEach((post) => {
237
+ entries.push({ slug: post.slug, updatedAt: post.updatedAt });
238
+ });
239
+ if (!pageData.pagination.hasMore) {
240
+ break;
241
+ }
242
+ page = pageData.pagination.page + 1;
243
+ }
244
+ return entries;
245
+ }
246
+ };
247
+ }
248
+
249
+ // src/sitemap.ts
250
+ function normalizeSiteUrl(url) {
251
+ return url.replace(/\/$/, "");
252
+ }
253
+ function normalizeRoutePrefix(routePrefix) {
254
+ if (!routePrefix || routePrefix === "/") {
255
+ return "";
256
+ }
257
+ const withSlash = routePrefix.startsWith("/") ? routePrefix : `/${routePrefix}`;
258
+ return withSlash.endsWith("/") ? withSlash.slice(0, -1) : withSlash;
259
+ }
260
+ function normalizeSlug(slug) {
261
+ return slug.replace(/^\/+/, "").replace(/\/+$/, "");
262
+ }
263
+ function buildSitemap(opts) {
264
+ const siteUrl = normalizeSiteUrl(opts.siteUrl);
265
+ const prefix = normalizeRoutePrefix(opts.routePrefix ?? "/blog");
266
+ const entries = opts.entries.map((entry) => {
267
+ const slug = normalizeSlug(entry.slug);
268
+ const hasSlug = slug.length > 0;
269
+ const slugFragment = hasSlug ? `/${slug}` : "";
270
+ const path = prefix ? `${prefix}${slugFragment}` : hasSlug ? `/${slug}` : "/";
271
+ return {
272
+ url: `${siteUrl}${path}`,
273
+ lastModified: entry.updatedAt
274
+ };
275
+ });
276
+ return entries;
277
+ }
278
+
279
+ // src/robots.ts
280
+ function normalizeSiteUrl2(url) {
281
+ return url.replace(/\/$/, "");
282
+ }
283
+ function normalizePath(path) {
284
+ if (!path) {
285
+ return "/sitemap.xml";
286
+ }
287
+ if (!path.startsWith("/")) {
288
+ return `/${path}`;
289
+ }
290
+ return path;
291
+ }
292
+ function buildRobots(opts) {
293
+ const siteUrl = normalizeSiteUrl2(opts.siteUrl);
294
+ const sitemapPath = normalizePath(opts.sitemapPath ?? "/sitemap.xml");
295
+ const sitemapUrl = `${siteUrl}${sitemapPath}`;
296
+ return {
297
+ rules: [{ userAgent: "*", allow: "/" }],
298
+ sitemap: sitemapUrl
299
+ };
300
+ }
301
+
302
+ // src/render.ts
303
+ var CODE_BLOCK_TOKEN = "__BLOGAUTO_CODE_BLOCK_";
304
+ function escapeHtml(value) {
305
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
306
+ }
307
+ function renderInline(input) {
308
+ let output = escapeHtml(input);
309
+ output = output.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>");
310
+ output = output.replace(/\*(.+?)\*/g, "<em>$1</em>");
311
+ output = output.replace(/`([^`]+)`/g, (_, code) => `<code>${code}</code>`);
312
+ output = output.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (_, alt, src) => {
313
+ const safeSrc = escapeHtml(src);
314
+ const safeAlt = escapeHtml(alt);
315
+ return `<img src="${safeSrc}" alt="${safeAlt}" />`;
316
+ });
317
+ output = output.replace(/\[(.+?)\]\((.+?)\)/g, (_, label, href) => {
318
+ const safeHref = escapeHtml(href);
319
+ return `<a href="${safeHref}" target="_blank" rel="noreferrer">${label}</a>`;
320
+ });
321
+ output = output.replace(/\n/g, "<br />");
322
+ return output;
323
+ }
324
+ function replaceCodeBlocks(markdown, blocks) {
325
+ return markdown.replace(/```([\s\S]*?)```/g, (_, code) => {
326
+ const index = blocks.push(`<pre><code>${escapeHtml(code.trim())}</code></pre>`) - 1;
327
+ return `${CODE_BLOCK_TOKEN}${index}__`;
328
+ });
329
+ }
330
+ function restoreCodeBlocks(html, blocks) {
331
+ return html.replace(/__BLOGAUTO_CODE_BLOCK_(\d+)__/g, (_, rawIndex) => blocks[Number(rawIndex)] ?? "");
332
+ }
333
+ function renderMarkdownToHtml(markdown) {
334
+ if (!markdown) {
335
+ return "";
336
+ }
337
+ const codeBlocks = [];
338
+ const withoutCode = replaceCodeBlocks(markdown, codeBlocks);
339
+ const blocks = withoutCode.replace(/\r\n/g, "\n").split(/\n{2,}/).map((block) => block.trim()).filter(Boolean);
340
+ const htmlBlocks = blocks.map((block) => {
341
+ const headingMatch = block.match(/^(#{1,6})\s+(.*)$/);
342
+ if (headingMatch) {
343
+ const level = headingMatch[1].length;
344
+ const content = renderInline(headingMatch[2]);
345
+ return `<h${level}>${content}</h${level}>`;
346
+ }
347
+ const imageMatch = block.match(/^!\[([^\]]*)\]\(([^)]+)\)$/);
348
+ if (imageMatch) {
349
+ const safeSrc = escapeHtml(imageMatch[2]);
350
+ const safeAlt = escapeHtml(imageMatch[1]);
351
+ return `<img src="${safeSrc}" alt="${safeAlt}" />`;
352
+ }
353
+ return `<p>${renderInline(block)}</p>`;
354
+ });
355
+ const html = htmlBlocks.join("\n");
356
+ return restoreCodeBlocks(html, codeBlocks);
357
+ }
358
+
359
+ // src/revalidate.ts
360
+ import crypto from "crypto";
361
+ function jsonResponse(body, init) {
362
+ const headers = new Headers(init?.headers);
363
+ headers.set("content-type", "application/json");
364
+ return new Response(JSON.stringify(body), {
365
+ ...init,
366
+ headers
367
+ });
368
+ }
369
+ function normalizeSignature(signature) {
370
+ if (!signature) {
371
+ return null;
372
+ }
373
+ const trimmed = signature.trim();
374
+ if (!trimmed) {
375
+ return null;
376
+ }
377
+ const withoutPrefix = trimmed.startsWith("sha256=") ? trimmed.slice(7) : trimmed;
378
+ if (!withoutPrefix) {
379
+ return null;
380
+ }
381
+ try {
382
+ return Buffer.from(withoutPrefix, "hex");
383
+ } catch {
384
+ return null;
385
+ }
386
+ }
387
+ function verifyWebhookSignature(opts) {
388
+ const { rawBody, signature, secret } = opts;
389
+ if (!secret) {
390
+ throw new Error("Secret is required to verify webhook signatures");
391
+ }
392
+ const providedSignature = normalizeSignature(signature);
393
+ if (!providedSignature) {
394
+ return false;
395
+ }
396
+ const bodyBuffer = typeof rawBody === "string" ? Buffer.from(rawBody) : rawBody;
397
+ const expected = crypto.createHmac("sha256", secret).update(bodyBuffer).digest();
398
+ if (providedSignature.length !== expected.length) {
399
+ return false;
400
+ }
401
+ return crypto.timingSafeEqual(providedSignature, expected);
402
+ }
403
+ function defaultPaths(payload) {
404
+ const paths = /* @__PURE__ */ new Set(["/sitemap.xml", "/robots.txt", "/blog"]);
405
+ if (payload.postSlug) {
406
+ paths.add(`/blog/${payload.postSlug}`);
407
+ }
408
+ return Array.from(paths);
409
+ }
410
+ function defaultTags(payload) {
411
+ const tags = /* @__PURE__ */ new Set();
412
+ if (payload.workspaceSlug) {
413
+ tags.add(`blogauto:${payload.workspaceSlug}:sitemap`);
414
+ tags.add(`blogauto:${payload.workspaceSlug}:posts`);
415
+ if (payload.postSlug) {
416
+ tags.add(`blogauto:${payload.workspaceSlug}:post:${payload.postSlug}`);
417
+ }
418
+ }
419
+ return Array.from(tags);
420
+ }
421
+ function dedupe(values) {
422
+ if (!values || values.length === 0) {
423
+ return [];
424
+ }
425
+ return Array.from(
426
+ new Set(
427
+ values.filter((value) => typeof value === "string" && value.trim().length > 0).map((value) => value.trim())
428
+ )
429
+ );
430
+ }
431
+ function determineRouteType(path) {
432
+ const lower = path.toLowerCase();
433
+ if (lower.endsWith(".xml") || lower.endsWith(".txt")) {
434
+ return "route";
435
+ }
436
+ return "page";
437
+ }
438
+ function createRevalidateRouteHandler(options) {
439
+ if (!options.secret) {
440
+ throw new Error("secret is required for createRevalidateRouteHandler");
441
+ }
442
+ const allowedSkewMs = Math.max(1, options.allowedSkewSeconds ?? 300) * 1e3;
443
+ const pathBuilder = options.revalidatePaths ?? defaultPaths;
444
+ const tagBuilder = options.revalidateTags ?? defaultTags;
445
+ return async function blogAutoRevalidateHandler(request) {
446
+ const signature = request.headers.get("x-blogauto-signature");
447
+ const receivedAt = Date.now();
448
+ const rawBody = await request.text();
449
+ const isValid = verifyWebhookSignature({
450
+ rawBody,
451
+ signature,
452
+ secret: options.secret
453
+ });
454
+ if (!isValid) {
455
+ return jsonResponse({ error: "Invalid webhook signature" }, { status: 401 });
456
+ }
457
+ let payload;
458
+ try {
459
+ payload = JSON.parse(rawBody);
460
+ } catch {
461
+ return jsonResponse({ error: "Invalid JSON payload" }, { status: 400 });
462
+ }
463
+ if (!payload.workspaceSlug || typeof payload.workspaceSlug !== "string") {
464
+ return jsonResponse({ error: "workspaceSlug is required" }, { status: 400 });
465
+ }
466
+ if (!payload.ts || typeof payload.ts !== "string") {
467
+ return jsonResponse({ error: "Timestamp is required" }, { status: 400 });
468
+ }
469
+ const payloadTs = Date.parse(payload.ts);
470
+ if (Number.isNaN(payloadTs)) {
471
+ return jsonResponse({ error: "Invalid timestamp" }, { status: 400 });
472
+ }
473
+ if (Math.abs(receivedAt - payloadTs) > allowedSkewMs) {
474
+ return jsonResponse({ error: "Webhook timestamp is outside allowed skew" }, { status: 409 });
475
+ }
476
+ const paths = dedupe(pathBuilder(payload));
477
+ const tags = dedupe(tagBuilder(payload));
478
+ try {
479
+ if (options.revalidatePath && paths.length > 0) {
480
+ await Promise.all(
481
+ paths.map((path) => options.revalidatePath(path, determineRouteType(path)))
482
+ );
483
+ }
484
+ if (options.revalidateTag && tags.length > 0) {
485
+ await Promise.all(tags.map((tag) => options.revalidateTag(tag)));
486
+ }
487
+ } catch (error) {
488
+ return jsonResponse(
489
+ { error: "Failed to revalidate cache", details: error.message },
490
+ { status: 500 }
491
+ );
492
+ }
493
+ return jsonResponse({
494
+ ok: true,
495
+ event: payload.event ?? "post.published",
496
+ revalidated: {
497
+ paths,
498
+ tags
499
+ }
500
+ });
501
+ };
502
+ }
503
+
504
+ // src/metadata.ts
505
+ function buildNextMetadata(post) {
506
+ const title = post.seo?.title ?? post.title;
507
+ const description = post.seo?.description ?? post.excerpt;
508
+ const canonical = post.metadata?.canonicalUrl;
509
+ const ogImageUrl = post.metadata?.ogImageUrl;
510
+ const metadata = {
511
+ title,
512
+ description
513
+ };
514
+ if (canonical) {
515
+ metadata.alternates = { canonical };
516
+ }
517
+ if (ogImageUrl) {
518
+ metadata.openGraph = {
519
+ title,
520
+ description,
521
+ images: [{ url: ogImageUrl }]
522
+ };
523
+ }
524
+ return metadata;
525
+ }
526
+ export {
527
+ ApiError,
528
+ BlogAutoError,
529
+ ConfigError,
530
+ DEFAULT_TIMEOUT_MS,
531
+ NotFoundError,
532
+ buildAuthHeaders,
533
+ buildNextMetadata,
534
+ buildRobots,
535
+ buildSitemap,
536
+ createBlogAutoClient,
537
+ createRevalidateRouteHandler,
538
+ renderMarkdownToHtml,
539
+ resolveClientConfig,
540
+ verifyWebhookSignature
541
+ };
542
+ //# sourceMappingURL=index.js.map