@autoblogwriter/sdk 1.0.2 → 2.0.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/README.md +10 -10
- package/dist/index.cjs +101 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +23 -19
- package/dist/index.d.ts +23 -19
- package/dist/index.js +100 -7
- package/dist/index.js.map +1 -1
- package/dist/metadata-DUQ3-Hhw.d.ts +29 -0
- package/dist/metadata-Ihd3IqKu.d.cts +29 -0
- package/dist/next.cjs +712 -0
- package/dist/next.cjs.map +1 -0
- package/dist/next.d.cts +17 -0
- package/dist/next.d.ts +17 -0
- package/dist/next.js +669 -0
- package/dist/next.js.map +1 -0
- package/dist/react.cjs +183 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +30 -0
- package/dist/react.d.ts +30 -0
- package/dist/react.js +154 -0
- package/dist/react.js.map +1 -0
- package/dist/revalidate.d.cts +19 -1
- package/dist/revalidate.d.ts +19 -1
- package/dist/styles.css +236 -0
- package/dist/{revalidate-445OJMx_.d.cts → types-CCDRs0sO.d.cts} +1 -17
- package/dist/{revalidate-445OJMx_.d.ts → types-CCDRs0sO.d.ts} +1 -17
- package/package.json +26 -2
package/dist/next.cjs
ADDED
|
@@ -0,0 +1,712 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/next/index.ts
|
|
31
|
+
var next_exports = {};
|
|
32
|
+
__export(next_exports, {
|
|
33
|
+
createEnvAnalyticsHandler: () => createEnvAnalyticsHandler,
|
|
34
|
+
createEnvRevalidateHandler: () => createEnvRevalidateHandler,
|
|
35
|
+
fetchBlogPost: () => fetchBlogPost,
|
|
36
|
+
fetchBlogPosts: () => fetchBlogPosts,
|
|
37
|
+
generateBlogRobots: () => generateBlogRobots,
|
|
38
|
+
generateBlogSitemap: () => generateBlogSitemap,
|
|
39
|
+
generatePostMetadata: () => generatePostMetadata
|
|
40
|
+
});
|
|
41
|
+
module.exports = __toCommonJS(next_exports);
|
|
42
|
+
|
|
43
|
+
// src/auth.ts
|
|
44
|
+
function buildAuthHeaders(apiKey, authMode = "bearer") {
|
|
45
|
+
if (authMode === "x-api-key") {
|
|
46
|
+
return { "x-api-key": apiKey };
|
|
47
|
+
}
|
|
48
|
+
return { Authorization: `Bearer ${apiKey}` };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/errors.ts
|
|
52
|
+
var BlogAutoError = class extends Error {
|
|
53
|
+
constructor(message, options) {
|
|
54
|
+
super(message);
|
|
55
|
+
this.name = new.target.name;
|
|
56
|
+
this.causeError = options?.cause;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
var ConfigError = class extends BlogAutoError {
|
|
60
|
+
};
|
|
61
|
+
var ApiError = class extends BlogAutoError {
|
|
62
|
+
constructor(message, info, options) {
|
|
63
|
+
super(message, options);
|
|
64
|
+
this.status = info.status;
|
|
65
|
+
this.code = info.code;
|
|
66
|
+
this.details = info.details;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// src/utils.ts
|
|
71
|
+
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
72
|
+
function normalizeApiUrl(apiUrl) {
|
|
73
|
+
if (!apiUrl) {
|
|
74
|
+
throw new ConfigError("apiUrl is required");
|
|
75
|
+
}
|
|
76
|
+
return apiUrl.replace(/\/$/, "");
|
|
77
|
+
}
|
|
78
|
+
function resolveClientConfig(config) {
|
|
79
|
+
if (!config) {
|
|
80
|
+
throw new ConfigError("Client configuration is required");
|
|
81
|
+
}
|
|
82
|
+
const apiKey = config.apiKey?.trim();
|
|
83
|
+
if (!apiKey) {
|
|
84
|
+
throw new ConfigError("apiKey is required to authenticate with BlogAuto");
|
|
85
|
+
}
|
|
86
|
+
if (!config.workspaceId && !config.workspaceSlug) {
|
|
87
|
+
throw new ConfigError("Provide either workspaceId or workspaceSlug");
|
|
88
|
+
}
|
|
89
|
+
const authMode = config.authMode ?? "bearer";
|
|
90
|
+
return {
|
|
91
|
+
apiKey,
|
|
92
|
+
apiUrl: normalizeApiUrl(config.apiUrl),
|
|
93
|
+
workspaceId: config.workspaceId,
|
|
94
|
+
workspaceSlug: config.workspaceSlug,
|
|
95
|
+
authMode,
|
|
96
|
+
fetch: config.fetch ?? globalThis.fetch.bind(globalThis),
|
|
97
|
+
headers: { ...config.headers ?? {} },
|
|
98
|
+
timeoutMs: config.timeoutMs ?? DEFAULT_TIMEOUT_MS
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function buildQuery(params) {
|
|
102
|
+
const query = new URLSearchParams();
|
|
103
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
104
|
+
if (value === void 0 || value === null || value === "") {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
query.set(key, String(value));
|
|
108
|
+
});
|
|
109
|
+
const qs = query.toString();
|
|
110
|
+
return qs ? `?${qs}` : "";
|
|
111
|
+
}
|
|
112
|
+
async function withTimeout(promise, timeoutMs) {
|
|
113
|
+
if (!timeoutMs) {
|
|
114
|
+
return promise;
|
|
115
|
+
}
|
|
116
|
+
let timeoutId;
|
|
117
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
118
|
+
timeoutId = setTimeout(() => {
|
|
119
|
+
reject(
|
|
120
|
+
new ApiError(`Request timed out after ${timeoutMs}ms`, {
|
|
121
|
+
status: 408
|
|
122
|
+
})
|
|
123
|
+
);
|
|
124
|
+
}, timeoutMs);
|
|
125
|
+
});
|
|
126
|
+
try {
|
|
127
|
+
return await Promise.race([promise, timeoutPromise]);
|
|
128
|
+
} finally {
|
|
129
|
+
clearTimeout(timeoutId);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function mergeHeaders(...headerObjects) {
|
|
133
|
+
return headerObjects.reduce((acc, headers) => {
|
|
134
|
+
if (!headers) {
|
|
135
|
+
return acc;
|
|
136
|
+
}
|
|
137
|
+
Object.entries(headers).forEach(([key, value]) => {
|
|
138
|
+
if (value === void 0 || value === null) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
acc[key.toLowerCase()] = value;
|
|
142
|
+
});
|
|
143
|
+
return acc;
|
|
144
|
+
}, {});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/client.ts
|
|
148
|
+
function createBlogAutoClient(config) {
|
|
149
|
+
const resolved = resolveClientConfig(config);
|
|
150
|
+
const fetchImpl = resolved.fetch;
|
|
151
|
+
const debug = typeof process !== "undefined" && typeof process.env !== "undefined" && process.env.BLOGAUTO_DEBUG === "true";
|
|
152
|
+
const debugLog = (...args) => {
|
|
153
|
+
if (debug) {
|
|
154
|
+
console.log("[blogauto]", ...args);
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
function ensureWorkspaceSlug() {
|
|
158
|
+
if (!resolved.workspaceSlug) {
|
|
159
|
+
throw new ConfigError("workspaceSlug is required to call the BlogAuto public API");
|
|
160
|
+
}
|
|
161
|
+
return resolved.workspaceSlug;
|
|
162
|
+
}
|
|
163
|
+
function buildUrl(path, query) {
|
|
164
|
+
const qs = buildQuery(query ?? {});
|
|
165
|
+
return `${resolved.apiUrl}${path}${qs}`;
|
|
166
|
+
}
|
|
167
|
+
function unwrapSuccessPayload(payload) {
|
|
168
|
+
if (payload && typeof payload === "object") {
|
|
169
|
+
const maybePayload = payload;
|
|
170
|
+
if ("success" in maybePayload) {
|
|
171
|
+
if (maybePayload.success === false) {
|
|
172
|
+
throw new ApiError(
|
|
173
|
+
maybePayload.error?.message ?? "BlogAuto request failed",
|
|
174
|
+
{ status: 500, details: maybePayload }
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
if (maybePayload.success && "data" in maybePayload) {
|
|
178
|
+
return maybePayload.data;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return payload;
|
|
183
|
+
}
|
|
184
|
+
async function request(opts) {
|
|
185
|
+
const method = opts.method ?? "GET";
|
|
186
|
+
const url = buildUrl(opts.path, opts.query);
|
|
187
|
+
const authHeaders = buildAuthHeaders(resolved.apiKey, resolved.authMode);
|
|
188
|
+
const baseHeaders = mergeHeaders(resolved.headers, authHeaders, opts.headers);
|
|
189
|
+
const init = {
|
|
190
|
+
method,
|
|
191
|
+
headers: baseHeaders
|
|
192
|
+
};
|
|
193
|
+
if (opts.body) {
|
|
194
|
+
init.body = typeof opts.body === "string" ? opts.body : JSON.stringify(opts.body);
|
|
195
|
+
init.headers = {
|
|
196
|
+
...baseHeaders,
|
|
197
|
+
"content-type": baseHeaders["content-type"] ?? "application/json"
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
if (opts.cache !== void 0) {
|
|
201
|
+
init.cache = opts.cache;
|
|
202
|
+
}
|
|
203
|
+
if (opts.next) {
|
|
204
|
+
init.next = opts.next;
|
|
205
|
+
}
|
|
206
|
+
const startedAt = Date.now();
|
|
207
|
+
try {
|
|
208
|
+
const response = await withTimeout(fetchImpl(url, init), resolved.timeoutMs);
|
|
209
|
+
debugLog("response", {
|
|
210
|
+
method,
|
|
211
|
+
url,
|
|
212
|
+
status: response.status,
|
|
213
|
+
durationMs: Date.now() - startedAt
|
|
214
|
+
});
|
|
215
|
+
if (opts.allowNotFound && response.status === 404) {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
219
|
+
const isJson = contentType.includes("application/json");
|
|
220
|
+
const payload = isJson ? await response.json().catch(() => void 0) : await response.text();
|
|
221
|
+
if (!response.ok) {
|
|
222
|
+
if (response.status === 404 && opts.allowNotFound) {
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
const errorBody = payload;
|
|
226
|
+
const message = errorBody?.error?.message ?? errorBody?.message ?? `Request failed with status ${response.status}`;
|
|
227
|
+
throw new ApiError(message, {
|
|
228
|
+
status: response.status,
|
|
229
|
+
code: errorBody?.error?.code ?? errorBody?.code,
|
|
230
|
+
details: errorBody?.error?.details ?? errorBody
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
if (isJson) {
|
|
234
|
+
return unwrapSuccessPayload(payload);
|
|
235
|
+
}
|
|
236
|
+
return payload ?? void 0;
|
|
237
|
+
} catch (error) {
|
|
238
|
+
debugLog("request failed", {
|
|
239
|
+
method,
|
|
240
|
+
url,
|
|
241
|
+
durationMs: Date.now() - startedAt,
|
|
242
|
+
error: error instanceof Error ? error.message : error
|
|
243
|
+
});
|
|
244
|
+
if (error instanceof BlogAutoError) {
|
|
245
|
+
throw error;
|
|
246
|
+
}
|
|
247
|
+
const causeMessage = error instanceof Error ? error.message : typeof error === "string" ? error : void 0;
|
|
248
|
+
const message = causeMessage ? `Network request to BlogAuto failed: ${causeMessage}` : "Network request to BlogAuto failed";
|
|
249
|
+
throw new ApiError(
|
|
250
|
+
message,
|
|
251
|
+
{
|
|
252
|
+
status: 0,
|
|
253
|
+
details: { url, method }
|
|
254
|
+
},
|
|
255
|
+
{ cause: error }
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
async function fetchBlogPage(limit, page, cacheOptions) {
|
|
260
|
+
const workspaceSlug = ensureWorkspaceSlug();
|
|
261
|
+
debugLog("fetch posts page", { workspaceSlug, limit, page });
|
|
262
|
+
return request({
|
|
263
|
+
path: `/v1/public/${encodeURIComponent(workspaceSlug)}/blogs`,
|
|
264
|
+
query: { limit, page },
|
|
265
|
+
cache: cacheOptions?.cache,
|
|
266
|
+
next: cacheOptions?.next
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
return {
|
|
270
|
+
async getPosts(params) {
|
|
271
|
+
const limit = params?.limit ?? 20;
|
|
272
|
+
const cursorPage = params?.cursor ? Number(params.cursor) : void 0;
|
|
273
|
+
const page = Number.isFinite(cursorPage) && cursorPage >= 1 ? cursorPage : 1;
|
|
274
|
+
const data = await fetchBlogPage(limit, page, params);
|
|
275
|
+
return {
|
|
276
|
+
posts: data.items,
|
|
277
|
+
nextCursor: data.pagination.hasMore ? String(data.pagination.page + 1) : void 0
|
|
278
|
+
};
|
|
279
|
+
},
|
|
280
|
+
async getPostBySlug(slug, options) {
|
|
281
|
+
if (!slug) {
|
|
282
|
+
throw new ApiError("slug is required", { status: 400 });
|
|
283
|
+
}
|
|
284
|
+
const workspaceSlug = ensureWorkspaceSlug();
|
|
285
|
+
const post = await request({
|
|
286
|
+
path: `/v1/public/${encodeURIComponent(workspaceSlug)}/blogs/${encodeURIComponent(slug)}`,
|
|
287
|
+
allowNotFound: true,
|
|
288
|
+
cache: options?.cache,
|
|
289
|
+
next: options?.next
|
|
290
|
+
});
|
|
291
|
+
if (post === null) {
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
return post.post;
|
|
295
|
+
},
|
|
296
|
+
async getSitemapEntries() {
|
|
297
|
+
const entries = [];
|
|
298
|
+
const limit = 100;
|
|
299
|
+
let page = 1;
|
|
300
|
+
while (true) {
|
|
301
|
+
const pageData = await fetchBlogPage(limit, page);
|
|
302
|
+
pageData.items.forEach((post) => {
|
|
303
|
+
entries.push({ slug: post.slug, updatedAt: post.updatedAt });
|
|
304
|
+
});
|
|
305
|
+
if (!pageData.pagination.hasMore) {
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
page = pageData.pagination.page + 1;
|
|
309
|
+
}
|
|
310
|
+
return entries;
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// src/env.ts
|
|
316
|
+
var _instance = null;
|
|
317
|
+
function requireEnv(name) {
|
|
318
|
+
const value = process.env[name];
|
|
319
|
+
if (!value) {
|
|
320
|
+
throw new Error(`Missing required environment variable: ${name}`);
|
|
321
|
+
}
|
|
322
|
+
return value;
|
|
323
|
+
}
|
|
324
|
+
function createBlogAutoFromEnv() {
|
|
325
|
+
if (_instance) {
|
|
326
|
+
return _instance;
|
|
327
|
+
}
|
|
328
|
+
const apiUrl = process.env.BLOGAUTO_API_URL ?? "https://api.blogauto.io";
|
|
329
|
+
const apiKey = requireEnv("BLOGAUTO_API_KEY");
|
|
330
|
+
const workspaceSlug = requireEnv("BLOGAUTO_WORKSPACE_SLUG");
|
|
331
|
+
const workspaceId = process.env.BLOGAUTO_WORKSPACE_ID;
|
|
332
|
+
const siteUrl = process.env.SITE_URL ?? process.env.NEXT_PUBLIC_SITE_URL ?? "http://localhost:3000";
|
|
333
|
+
const revalidateSecret = process.env.BLOGAUTO_REVALIDATE_SECRET;
|
|
334
|
+
const analyticsToken = process.env.NEXT_PUBLIC_ANALYTICS_TOKEN;
|
|
335
|
+
const analyticsProxy = process.env.NEXT_PUBLIC_ANALYTICS_PROXY ?? "/api/blogauto/analytics";
|
|
336
|
+
const client = createBlogAutoClient({
|
|
337
|
+
apiUrl,
|
|
338
|
+
apiKey,
|
|
339
|
+
workspaceSlug,
|
|
340
|
+
workspaceId
|
|
341
|
+
});
|
|
342
|
+
_instance = {
|
|
343
|
+
client,
|
|
344
|
+
workspaceSlug,
|
|
345
|
+
workspaceId,
|
|
346
|
+
siteUrl,
|
|
347
|
+
revalidateSecret,
|
|
348
|
+
analyticsToken,
|
|
349
|
+
analyticsProxy,
|
|
350
|
+
apiUrl,
|
|
351
|
+
apiKey,
|
|
352
|
+
tags: {
|
|
353
|
+
posts: `blogauto:${workspaceSlug}:posts`,
|
|
354
|
+
post: (slug) => `blogauto:${workspaceSlug}:post:${slug}`,
|
|
355
|
+
sitemap: `blogauto:${workspaceSlug}:sitemap`
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
return _instance;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// src/metadata.ts
|
|
362
|
+
function buildNextMetadata(post) {
|
|
363
|
+
const title = post.seo?.title ?? post.title;
|
|
364
|
+
const description = post.seo?.description ?? post.excerpt;
|
|
365
|
+
const keywords = post.seo?.keywords;
|
|
366
|
+
const canonical = post.metadata?.canonicalUrl;
|
|
367
|
+
const ogImageUrl = post.metadata?.ogImageUrl;
|
|
368
|
+
const metadata = {
|
|
369
|
+
title,
|
|
370
|
+
description
|
|
371
|
+
};
|
|
372
|
+
if (keywords && keywords.length > 0) {
|
|
373
|
+
metadata.keywords = keywords;
|
|
374
|
+
}
|
|
375
|
+
if (canonical) {
|
|
376
|
+
metadata.alternates = { canonical };
|
|
377
|
+
}
|
|
378
|
+
metadata.openGraph = {
|
|
379
|
+
type: "article",
|
|
380
|
+
title,
|
|
381
|
+
description,
|
|
382
|
+
publishedTime: post.publishedAt,
|
|
383
|
+
modifiedTime: post.updatedAt
|
|
384
|
+
};
|
|
385
|
+
if (ogImageUrl) {
|
|
386
|
+
metadata.openGraph.images = [{ url: ogImageUrl }];
|
|
387
|
+
}
|
|
388
|
+
metadata.twitter = {
|
|
389
|
+
card: ogImageUrl ? "summary_large_image" : "summary",
|
|
390
|
+
title,
|
|
391
|
+
description
|
|
392
|
+
};
|
|
393
|
+
if (ogImageUrl) {
|
|
394
|
+
metadata.twitter.images = [ogImageUrl];
|
|
395
|
+
}
|
|
396
|
+
return metadata;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// src/sitemap.ts
|
|
400
|
+
function normalizeSiteUrl(url) {
|
|
401
|
+
return url.replace(/\/$/, "");
|
|
402
|
+
}
|
|
403
|
+
function normalizeRoutePrefix(routePrefix) {
|
|
404
|
+
if (!routePrefix || routePrefix === "/") {
|
|
405
|
+
return "";
|
|
406
|
+
}
|
|
407
|
+
const withSlash = routePrefix.startsWith("/") ? routePrefix : `/${routePrefix}`;
|
|
408
|
+
return withSlash.endsWith("/") ? withSlash.slice(0, -1) : withSlash;
|
|
409
|
+
}
|
|
410
|
+
function normalizeSlug(slug) {
|
|
411
|
+
return slug.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
412
|
+
}
|
|
413
|
+
function buildSitemap(opts) {
|
|
414
|
+
const siteUrl = normalizeSiteUrl(opts.siteUrl);
|
|
415
|
+
const prefix = normalizeRoutePrefix(opts.routePrefix ?? "/blog");
|
|
416
|
+
const entries = opts.entries.map((entry) => {
|
|
417
|
+
const slug = normalizeSlug(entry.slug);
|
|
418
|
+
const hasSlug = slug.length > 0;
|
|
419
|
+
const slugFragment = hasSlug ? `/${slug}` : "";
|
|
420
|
+
const path = prefix ? `${prefix}${slugFragment}` : hasSlug ? `/${slug}` : "/";
|
|
421
|
+
return {
|
|
422
|
+
url: `${siteUrl}${path}`,
|
|
423
|
+
lastModified: entry.updatedAt
|
|
424
|
+
};
|
|
425
|
+
});
|
|
426
|
+
return entries;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// src/robots.ts
|
|
430
|
+
function normalizeSiteUrl2(url) {
|
|
431
|
+
return url.replace(/\/$/, "");
|
|
432
|
+
}
|
|
433
|
+
function normalizePath(path) {
|
|
434
|
+
if (!path) {
|
|
435
|
+
return "/sitemap.xml";
|
|
436
|
+
}
|
|
437
|
+
if (!path.startsWith("/")) {
|
|
438
|
+
return `/${path}`;
|
|
439
|
+
}
|
|
440
|
+
return path;
|
|
441
|
+
}
|
|
442
|
+
function buildRobots(opts) {
|
|
443
|
+
const siteUrl = normalizeSiteUrl2(opts.siteUrl);
|
|
444
|
+
const sitemapPath = normalizePath(opts.sitemapPath ?? "/sitemap.xml");
|
|
445
|
+
const sitemapUrl = `${siteUrl}${sitemapPath}`;
|
|
446
|
+
return {
|
|
447
|
+
rules: [{ userAgent: "*", allow: "/" }],
|
|
448
|
+
sitemap: sitemapUrl
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// src/next/helpers.ts
|
|
453
|
+
async function fetchBlogPosts(options) {
|
|
454
|
+
const { client, tags } = createBlogAutoFromEnv();
|
|
455
|
+
return client.getPosts({
|
|
456
|
+
limit: options?.limit,
|
|
457
|
+
cursor: options?.cursor,
|
|
458
|
+
next: { tags: [tags.posts] }
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
async function fetchBlogPost(slug) {
|
|
462
|
+
const { client, tags } = createBlogAutoFromEnv();
|
|
463
|
+
const post = await client.getPostBySlug(slug, {
|
|
464
|
+
next: { tags: [tags.post(slug)] }
|
|
465
|
+
});
|
|
466
|
+
if (!post) {
|
|
467
|
+
const nav = await import(
|
|
468
|
+
/* webpackIgnore: true */
|
|
469
|
+
"next/navigation"
|
|
470
|
+
);
|
|
471
|
+
nav.notFound();
|
|
472
|
+
throw new Error("notFound");
|
|
473
|
+
}
|
|
474
|
+
return post;
|
|
475
|
+
}
|
|
476
|
+
async function generatePostMetadata(slug) {
|
|
477
|
+
const { client, tags } = createBlogAutoFromEnv();
|
|
478
|
+
const post = await client.getPostBySlug(slug, {
|
|
479
|
+
next: { tags: [tags.post(slug)] }
|
|
480
|
+
});
|
|
481
|
+
if (!post) {
|
|
482
|
+
return {};
|
|
483
|
+
}
|
|
484
|
+
return buildNextMetadata(post);
|
|
485
|
+
}
|
|
486
|
+
async function generateBlogSitemap() {
|
|
487
|
+
const { client, siteUrl } = createBlogAutoFromEnv();
|
|
488
|
+
const entries = await client.getSitemapEntries();
|
|
489
|
+
return buildSitemap({ siteUrl, routePrefix: "/blog", entries });
|
|
490
|
+
}
|
|
491
|
+
function generateBlogRobots() {
|
|
492
|
+
const { siteUrl } = createBlogAutoFromEnv();
|
|
493
|
+
return buildRobots({ siteUrl, sitemapPath: "/sitemap.xml" });
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// src/revalidate.ts
|
|
497
|
+
var import_crypto = __toESM(require("crypto"), 1);
|
|
498
|
+
function jsonResponse(body, init) {
|
|
499
|
+
const headers = new Headers(init?.headers);
|
|
500
|
+
headers.set("content-type", "application/json");
|
|
501
|
+
return new Response(JSON.stringify(body), {
|
|
502
|
+
...init,
|
|
503
|
+
headers
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
function normalizeSignature(signature) {
|
|
507
|
+
if (!signature) {
|
|
508
|
+
return null;
|
|
509
|
+
}
|
|
510
|
+
const trimmed = signature.trim();
|
|
511
|
+
if (!trimmed) {
|
|
512
|
+
return null;
|
|
513
|
+
}
|
|
514
|
+
const withoutPrefix = trimmed.startsWith("sha256=") ? trimmed.slice(7) : trimmed;
|
|
515
|
+
if (!withoutPrefix) {
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
try {
|
|
519
|
+
return Buffer.from(withoutPrefix, "hex");
|
|
520
|
+
} catch {
|
|
521
|
+
return null;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
function verifyWebhookSignature(opts) {
|
|
525
|
+
const { rawBody, signature, secret } = opts;
|
|
526
|
+
if (!secret) {
|
|
527
|
+
throw new Error("Secret is required to verify webhook signatures");
|
|
528
|
+
}
|
|
529
|
+
const providedSignature = normalizeSignature(signature);
|
|
530
|
+
if (!providedSignature) {
|
|
531
|
+
return false;
|
|
532
|
+
}
|
|
533
|
+
const bodyBuffer = typeof rawBody === "string" ? Buffer.from(rawBody) : rawBody;
|
|
534
|
+
const expected = import_crypto.default.createHmac("sha256", secret).update(bodyBuffer).digest();
|
|
535
|
+
if (providedSignature.length !== expected.length) {
|
|
536
|
+
return false;
|
|
537
|
+
}
|
|
538
|
+
return import_crypto.default.timingSafeEqual(providedSignature, expected);
|
|
539
|
+
}
|
|
540
|
+
function defaultPaths(payload) {
|
|
541
|
+
const paths = /* @__PURE__ */ new Set(["/sitemap.xml", "/robots.txt", "/blog"]);
|
|
542
|
+
if (payload.postSlug) {
|
|
543
|
+
paths.add(`/blog/${payload.postSlug}`);
|
|
544
|
+
}
|
|
545
|
+
return Array.from(paths);
|
|
546
|
+
}
|
|
547
|
+
function defaultTags(payload) {
|
|
548
|
+
const tags = /* @__PURE__ */ new Set();
|
|
549
|
+
if (payload.workspaceSlug) {
|
|
550
|
+
tags.add(`blogauto:${payload.workspaceSlug}:sitemap`);
|
|
551
|
+
tags.add(`blogauto:${payload.workspaceSlug}:posts`);
|
|
552
|
+
if (payload.postSlug) {
|
|
553
|
+
tags.add(`blogauto:${payload.workspaceSlug}:post:${payload.postSlug}`);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
return Array.from(tags);
|
|
557
|
+
}
|
|
558
|
+
function dedupe(values) {
|
|
559
|
+
if (!values || values.length === 0) {
|
|
560
|
+
return [];
|
|
561
|
+
}
|
|
562
|
+
return Array.from(
|
|
563
|
+
new Set(
|
|
564
|
+
values.filter((value) => typeof value === "string" && value.trim().length > 0).map((value) => value.trim())
|
|
565
|
+
)
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
function determineRouteType(path) {
|
|
569
|
+
const lower = path.toLowerCase();
|
|
570
|
+
if (lower.endsWith(".xml") || lower.endsWith(".txt")) {
|
|
571
|
+
return "route";
|
|
572
|
+
}
|
|
573
|
+
return "page";
|
|
574
|
+
}
|
|
575
|
+
function createRevalidateRouteHandler(options) {
|
|
576
|
+
if (!options.secret) {
|
|
577
|
+
throw new Error("secret is required for createRevalidateRouteHandler");
|
|
578
|
+
}
|
|
579
|
+
const allowedSkewMs = Math.max(1, options.allowedSkewSeconds ?? 300) * 1e3;
|
|
580
|
+
const pathBuilder = options.revalidatePaths ?? defaultPaths;
|
|
581
|
+
const tagBuilder = options.revalidateTags ?? defaultTags;
|
|
582
|
+
return async function blogAutoRevalidateHandler(request) {
|
|
583
|
+
const signature = request.headers.get("x-blogauto-signature");
|
|
584
|
+
const receivedAt = Date.now();
|
|
585
|
+
const rawBody = await request.text();
|
|
586
|
+
const isValid = verifyWebhookSignature({
|
|
587
|
+
rawBody,
|
|
588
|
+
signature,
|
|
589
|
+
secret: options.secret
|
|
590
|
+
});
|
|
591
|
+
if (!isValid) {
|
|
592
|
+
return jsonResponse({ error: "Invalid webhook signature" }, { status: 401 });
|
|
593
|
+
}
|
|
594
|
+
let payload;
|
|
595
|
+
try {
|
|
596
|
+
payload = JSON.parse(rawBody);
|
|
597
|
+
} catch {
|
|
598
|
+
return jsonResponse({ error: "Invalid JSON payload" }, { status: 400 });
|
|
599
|
+
}
|
|
600
|
+
if (!payload.workspaceSlug || typeof payload.workspaceSlug !== "string") {
|
|
601
|
+
return jsonResponse({ error: "workspaceSlug is required" }, { status: 400 });
|
|
602
|
+
}
|
|
603
|
+
if (!payload.ts || typeof payload.ts !== "string") {
|
|
604
|
+
return jsonResponse({ error: "Timestamp is required" }, { status: 400 });
|
|
605
|
+
}
|
|
606
|
+
const payloadTs = Date.parse(payload.ts);
|
|
607
|
+
if (Number.isNaN(payloadTs)) {
|
|
608
|
+
return jsonResponse({ error: "Invalid timestamp" }, { status: 400 });
|
|
609
|
+
}
|
|
610
|
+
if (Math.abs(receivedAt - payloadTs) > allowedSkewMs) {
|
|
611
|
+
return jsonResponse({ error: "Webhook timestamp is outside allowed skew" }, { status: 409 });
|
|
612
|
+
}
|
|
613
|
+
const paths = dedupe(pathBuilder(payload));
|
|
614
|
+
const tags = dedupe(tagBuilder(payload));
|
|
615
|
+
try {
|
|
616
|
+
if (options.revalidatePath && paths.length > 0) {
|
|
617
|
+
await Promise.all(
|
|
618
|
+
paths.map((path) => options.revalidatePath(path, determineRouteType(path)))
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
if (options.revalidateTag && tags.length > 0) {
|
|
622
|
+
await Promise.all(tags.map((tag) => options.revalidateTag(tag)));
|
|
623
|
+
}
|
|
624
|
+
} catch (error) {
|
|
625
|
+
return jsonResponse(
|
|
626
|
+
{ error: "Failed to revalidate cache", details: error.message },
|
|
627
|
+
{ status: 500 }
|
|
628
|
+
);
|
|
629
|
+
}
|
|
630
|
+
return jsonResponse({
|
|
631
|
+
ok: true,
|
|
632
|
+
event: payload.event ?? "post.published",
|
|
633
|
+
revalidated: {
|
|
634
|
+
paths,
|
|
635
|
+
tags
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// src/next/route-handlers.ts
|
|
642
|
+
function createEnvRevalidateHandler() {
|
|
643
|
+
return async (request) => {
|
|
644
|
+
const { revalidateSecret } = createBlogAutoFromEnv();
|
|
645
|
+
if (!revalidateSecret) {
|
|
646
|
+
return new Response(
|
|
647
|
+
JSON.stringify({ error: "Missing BLOGAUTO_REVALIDATE_SECRET" }),
|
|
648
|
+
{ status: 500, headers: { "content-type": "application/json" } }
|
|
649
|
+
);
|
|
650
|
+
}
|
|
651
|
+
const nextCache = await import(
|
|
652
|
+
/* webpackIgnore: true */
|
|
653
|
+
"next/cache"
|
|
654
|
+
);
|
|
655
|
+
const { revalidatePath, revalidateTag } = nextCache;
|
|
656
|
+
const handler = createRevalidateRouteHandler({
|
|
657
|
+
secret: revalidateSecret,
|
|
658
|
+
revalidatePath,
|
|
659
|
+
revalidateTag
|
|
660
|
+
});
|
|
661
|
+
return handler(request);
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
function createEnvAnalyticsHandler() {
|
|
665
|
+
return async (request) => {
|
|
666
|
+
const { analyticsToken, apiUrl, apiKey, workspaceId, workspaceSlug } = createBlogAutoFromEnv();
|
|
667
|
+
if (!analyticsToken) {
|
|
668
|
+
return new Response(
|
|
669
|
+
JSON.stringify({ error: "Analytics proxy disabled" }),
|
|
670
|
+
{ status: 503, headers: { "content-type": "application/json" } }
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
const provided = request.headers.get("x-api-key");
|
|
674
|
+
if (provided !== analyticsToken) {
|
|
675
|
+
return new Response(
|
|
676
|
+
JSON.stringify({ error: "Unauthorized" }),
|
|
677
|
+
{ status: 401, headers: { "content-type": "application/json" } }
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
const payload = await request.json();
|
|
681
|
+
const envelope = {
|
|
682
|
+
...payload,
|
|
683
|
+
workspaceId: workspaceId ?? payload.workspaceId,
|
|
684
|
+
workspaceSlug: workspaceSlug ?? payload.workspaceSlug
|
|
685
|
+
};
|
|
686
|
+
const response = await fetch(`${apiUrl}/v1/analytics/ingest`, {
|
|
687
|
+
method: "POST",
|
|
688
|
+
headers: {
|
|
689
|
+
"content-type": "application/json",
|
|
690
|
+
...buildAuthHeaders(apiKey, "bearer")
|
|
691
|
+
},
|
|
692
|
+
body: JSON.stringify(envelope)
|
|
693
|
+
});
|
|
694
|
+
const text = await response.text();
|
|
695
|
+
const contentType = response.headers.get("content-type") ?? "application/json";
|
|
696
|
+
return new Response(text || "{}", {
|
|
697
|
+
status: response.status,
|
|
698
|
+
headers: { "content-type": contentType }
|
|
699
|
+
});
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
703
|
+
0 && (module.exports = {
|
|
704
|
+
createEnvAnalyticsHandler,
|
|
705
|
+
createEnvRevalidateHandler,
|
|
706
|
+
fetchBlogPost,
|
|
707
|
+
fetchBlogPosts,
|
|
708
|
+
generateBlogRobots,
|
|
709
|
+
generateBlogSitemap,
|
|
710
|
+
generatePostMetadata
|
|
711
|
+
});
|
|
712
|
+
//# sourceMappingURL=next.cjs.map
|