@botlr/next 0.0.0-stage → 0.2.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/index.cjs ADDED
@@ -0,0 +1,681 @@
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 __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+
31
+ // src/index.ts
32
+ var index_exports = {};
33
+ __export(index_exports, {
34
+ BOTLR_ENV: () => BOTLR_ENV,
35
+ BotlrConflictError: () => import_sdk2.BotlrConflictError,
36
+ BotlrError: () => import_sdk2.BotlrError,
37
+ DEFAULT_API_URL: () => DEFAULT_API_URL,
38
+ buildPageMetadata: () => buildPageMetadata,
39
+ collectSharedSectionIds: () => collectSharedSectionIds,
40
+ configFromEnv: () => configFromEnv,
41
+ createAssetUrl: () => import_sdk2.createAssetUrl,
42
+ createBotlrClient: () => import_sdk2.createBotlrClient,
43
+ createBotlrFromEnv: () => createBotlrFromEnv,
44
+ createLocaleAliasPage: () => createLocaleAliasPage,
45
+ createLocalePage: () => createLocalePage,
46
+ createPageResolver: () => createPageResolver,
47
+ defaultRobotsContent: () => defaultRobotsContent,
48
+ environmentFromEnv: () => environmentFromEnv,
49
+ getAssetFilename: () => import_sdk2.getAssetFilename,
50
+ getAssetId: () => import_sdk2.getAssetId,
51
+ getAssetUrl: () => import_sdk2.getAssetUrl,
52
+ getBotlrForRoute: () => getBotlrForRoute,
53
+ getCachedBotlr: () => getCachedBotlr,
54
+ getClientIp: () => getClientIp,
55
+ isAssetData: () => import_sdk2.isAssetData,
56
+ localeFromAcceptLanguage: () => localeFromAcceptLanguage,
57
+ mergeTemplateLayout: () => mergeTemplateLayout,
58
+ pageSlugFromParams: () => pageSlugFromParams,
59
+ prepareTemplate: () => prepareTemplate,
60
+ rateLimit: () => rateLimit,
61
+ renderCmsPage: () => renderCmsPage,
62
+ resolvePage: () => resolvePage,
63
+ scopeFromEnv: () => scopeFromEnv,
64
+ serveStaticTextFile: () => serveStaticTextFile,
65
+ themeSettingsToCss: () => themeSettingsToCss
66
+ });
67
+ module.exports = __toCommonJS(index_exports);
68
+
69
+ // src/server.ts
70
+ var import_headers = require("next/headers");
71
+ var import_react = require("react");
72
+ var import_sdk = require("@botlr/sdk");
73
+
74
+ // src/env.ts
75
+ var BOTLR_ENV = {
76
+ apiUrl: "BOTLR_API_URL",
77
+ apiKey: "BOTLR_API_KEY",
78
+ appId: "BOTLR_APP_ID",
79
+ pat: "BOTLR_PAT",
80
+ orgSlug: "BOTLR_ORG_SLUG",
81
+ projectSlug: "BOTLR_PROJECT_SLUG",
82
+ appInstanceSlug: "BOTLR_APP_INSTANCE_SLUG",
83
+ environment: "BOTLR_ENVIRONMENT"
84
+ };
85
+ var DEFAULT_API_URL = "http://localhost:8888";
86
+ function env(name) {
87
+ const value = typeof process !== "undefined" ? process.env?.[name] : void 0;
88
+ return value === "" ? void 0 : value;
89
+ }
90
+ function configFromEnv(overrides = {}) {
91
+ const appId = overrides.appId ?? env(BOTLR_ENV.appId);
92
+ if (!appId) {
93
+ throw new Error(`@botlr/next: pass appId or set ${BOTLR_ENV.appId}`);
94
+ }
95
+ return {
96
+ ...overrides,
97
+ baseUrl: overrides.baseUrl ?? env(BOTLR_ENV.apiUrl) ?? DEFAULT_API_URL,
98
+ appId,
99
+ apiKey: overrides.apiKey ?? env(BOTLR_ENV.apiKey),
100
+ personalToken: overrides.personalToken ?? env(BOTLR_ENV.pat)
101
+ };
102
+ }
103
+ function environmentFromEnv(fallback = "production") {
104
+ return env(BOTLR_ENV.environment) ?? env("ENVIRONMENT") ?? env("NODE_ENV") ?? fallback;
105
+ }
106
+ function scopeFromEnv(overrides) {
107
+ return {
108
+ orgSlug: env(BOTLR_ENV.orgSlug) ?? "",
109
+ projectSlug: env(BOTLR_ENV.projectSlug) ?? "",
110
+ appInstanceSlug: env(BOTLR_ENV.appInstanceSlug) ?? "",
111
+ appInstanceId: env(BOTLR_ENV.appId) ?? "",
112
+ ...overrides
113
+ };
114
+ }
115
+
116
+ // src/locale.ts
117
+ function localeFromAcceptLanguage(header, fallback = "en") {
118
+ if (!header) return fallback;
119
+ const first = header.split(",")[0]?.split(";")[0]?.trim();
120
+ return first || fallback;
121
+ }
122
+
123
+ // src/server.ts
124
+ async function getBotlrForRoute(params = {}) {
125
+ const h = await (0, import_headers.headers)();
126
+ const locale = params.locale || localeFromAcceptLanguage(h.get("accept-language"));
127
+ return (0, import_sdk.createBotlrClient)(
128
+ configFromEnv({
129
+ appId: params.appId,
130
+ apiKey: params.apiKey,
131
+ baseUrl: params.baseUrl,
132
+ previewToken: params.previewToken,
133
+ locale,
134
+ preview: params.preview,
135
+ fetch: params.fetch
136
+ })
137
+ );
138
+ }
139
+ var getCachedBotlr = (0, import_react.cache)(getBotlrForRoute);
140
+ function createBotlrFromEnv(overrides = {}) {
141
+ return (0, import_sdk.createBotlrClient)(configFromEnv(overrides));
142
+ }
143
+
144
+ // src/resolve-page.ts
145
+ var import_react2 = require("react");
146
+ var DEFAULT_CONTENT_SECTIONS = { article: "article" };
147
+ async function resolvePage(client, slug, locale, options = {}) {
148
+ try {
149
+ const result = await client.resolveUrl(slug, locale);
150
+ if (result.kind === "404") return { kind: "404" };
151
+ if (result.kind === "redirect" && result.redirect) {
152
+ return { kind: "redirect", destination: result.redirect.destination };
153
+ }
154
+ if (!result.data) return { kind: "404" };
155
+ const data = result.data;
156
+ const layout = data.layout ?? {};
157
+ const content = data.content ?? {};
158
+ const contentType = content["type"] ?? data.type ?? null;
159
+ if (contentType && contentType !== "page") {
160
+ const sectionType = (options.contentSections ?? DEFAULT_CONTENT_SECTIONS)[contentType];
161
+ if (sectionType) {
162
+ const section = {
163
+ _key: `${sectionType}-${content["id"] ?? data.id}`,
164
+ mode: "inline",
165
+ sectionType,
166
+ props: content,
167
+ status: "published"
168
+ };
169
+ layout["main"] = [section, ...layout["main"] ?? []];
170
+ } else {
171
+ console.warn("Unknown content type:", contentType);
172
+ }
173
+ }
174
+ return {
175
+ kind: "page",
176
+ pageData: {
177
+ id: data.id,
178
+ name: data.name,
179
+ slug: data.slug || "",
180
+ full_slug: data.full_slug || "",
181
+ layout,
182
+ status: data.status,
183
+ theme: data.theme,
184
+ page_template: data.page_template,
185
+ seo_meta: data.seo_meta,
186
+ alternate_translations: result.alternate_translations ?? [],
187
+ breadcrumbs: result.breadcrumbs ?? []
188
+ }
189
+ };
190
+ } catch (error) {
191
+ return {
192
+ kind: "error",
193
+ message: error instanceof Error ? error.message : "Unknown error"
194
+ };
195
+ }
196
+ }
197
+ function createPageResolver(client, options = {}) {
198
+ return (0, import_react2.cache)((slug, locale) => resolvePage(client, slug, locale, options));
199
+ }
200
+
201
+ // src/page-metadata.ts
202
+ function buildPageMetadata(pageData, options = {}) {
203
+ const seo = pageData.seo_meta || {};
204
+ const title = seo.title || pageData.name;
205
+ const alternates = pageData.alternate_translations || [];
206
+ const siteUrl = options.siteUrl?.replace(/\/+$/, "");
207
+ const folder = options.folder ? `/${options.folder.replace(/^\/+|\/+$/g, "")}` : "";
208
+ const metadata = { title };
209
+ if (seo.description) metadata.description = seo.description;
210
+ if (seo.keywords) metadata.keywords = seo.keywords;
211
+ const ogImage = seo.ogImage?.url || seo.ogImage?.filename;
212
+ const description = seo.ogDescription || seo.description;
213
+ metadata.openGraph = {
214
+ title: seo.ogTitle || title,
215
+ ...description ? { description } : {},
216
+ ...seo.ogType ? { type: seo.ogType } : {},
217
+ ...ogImage ? { images: [ogImage] } : {}
218
+ };
219
+ if (seo.twitterCard) {
220
+ metadata.twitter = {
221
+ card: seo.twitterCard,
222
+ title: seo.ogTitle || title,
223
+ ...description ? { description } : {},
224
+ ...ogImage ? { images: [ogImage] } : {}
225
+ };
226
+ }
227
+ if (seo.noindex !== void 0 || seo.nofollow !== void 0 || seo.robots) {
228
+ metadata.robots = seo.robots ?? { index: !seo.noindex, follow: !seo.nofollow };
229
+ }
230
+ if (seo.canonical) {
231
+ metadata.alternates = { canonical: seo.canonical };
232
+ } else if (siteUrl) {
233
+ const canonical = pageData.full_slug ? `${siteUrl}${folder}/${pageData.full_slug}` : `${siteUrl}${folder}`;
234
+ metadata.alternates = { canonical };
235
+ }
236
+ if (siteUrl && alternates.length > 0) {
237
+ const languages = {};
238
+ for (const alt of alternates) {
239
+ languages[alt.locale] = `${siteUrl}${alt.full_slug}`;
240
+ }
241
+ metadata.alternates = { ...metadata.alternates, languages };
242
+ }
243
+ return metadata;
244
+ }
245
+
246
+ // src/render-cms-page.tsx
247
+ var import_html_react_parser = __toESM(require("html-react-parser"), 1);
248
+ var import_react3 = require("@botlr/react");
249
+
250
+ // src/theme-css.ts
251
+ var COLOR_KEYS = [
252
+ "primary",
253
+ "primaryForeground",
254
+ "secondary",
255
+ "secondaryForeground",
256
+ "accent",
257
+ "accentForeground",
258
+ "background",
259
+ "foreground",
260
+ "muted",
261
+ "mutedForeground",
262
+ "border",
263
+ "input",
264
+ "ring",
265
+ "destructive",
266
+ "destructiveForeground",
267
+ "success",
268
+ "successForeground",
269
+ "warning",
270
+ "warningForeground",
271
+ "info",
272
+ "infoForeground"
273
+ ];
274
+ var COMMON_GROUPS = [
275
+ [["typography", "fontFamily"], "--font", ["heading", "body", "ui", "code", "sans", "serif", "mono"]],
276
+ [["typography", "fontSize"], "--font-size", ["h1", "h2", "h3", "h4", "h5", "h6", "xs", "sm", "base", "lg", "xl", "2xl", "3xl", "4xl"]],
277
+ [["typography", "fontWeight"], "--font-weight", ["normal", "medium", "semibold", "bold"]],
278
+ [["typography", "lineHeight"], "--line-height", ["tight", "normal", "relaxed"]],
279
+ [["spacing"], "--spacing", ["xs", "sm", "md", "lg", "xl", "2xl"]],
280
+ [["borderRadius"], "--radius", ["none", "sm", "md", "lg", "xl", "full"]],
281
+ [["shadows"], "--shadow", ["sm", "md", "lg", "xl"]],
282
+ [["layout"], "-", ["containerMaxWidth", "sidebarWidth", "headerHeight", "footerHeight"]]
283
+ ];
284
+ var kebab = (key) => key.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
285
+ function colorVars(colors) {
286
+ if (!colors) return [];
287
+ const vars = [];
288
+ for (const key of COLOR_KEYS) {
289
+ const value = colors[key];
290
+ if (value) vars.push(`--color-${kebab(key)}: ${value};`);
291
+ }
292
+ return vars;
293
+ }
294
+ function pick(settings, path) {
295
+ let current = settings;
296
+ for (const segment of path) {
297
+ current = current?.[segment];
298
+ if (!current) return void 0;
299
+ }
300
+ return current;
301
+ }
302
+ function themeSettingsToCss(settings) {
303
+ if (!settings) return "";
304
+ const s = settings;
305
+ const lightVars = colorVars(s.colors?.light);
306
+ const darkVars = colorVars(s.colors?.dark);
307
+ const commonVars = [];
308
+ for (const [path, prefix, keys] of COMMON_GROUPS) {
309
+ const group = pick(settings, path);
310
+ if (!group) continue;
311
+ for (const key of keys) {
312
+ const value = group[key];
313
+ if (value) commonVars.push(`${prefix}-${kebab(key)}: ${value};`);
314
+ }
315
+ }
316
+ const cssBlocks = [];
317
+ if (commonVars.length > 0) {
318
+ cssBlocks.push(`:root {
319
+ ${commonVars.join("\n ")}
320
+ }`);
321
+ }
322
+ if (lightVars.length > 0) {
323
+ cssBlocks.push(`:root {
324
+ ${lightVars.join("\n ")}
325
+ }`);
326
+ }
327
+ if (darkVars.length > 0) {
328
+ cssBlocks.push(`@media (prefers-color-scheme: dark) {
329
+ :root {
330
+ ${darkVars.join("\n ")}
331
+ }
332
+ }`);
333
+ cssBlocks.push(`.dark {
334
+ ${darkVars.join("\n ")}
335
+ }`);
336
+ }
337
+ return cssBlocks.join("\n\n");
338
+ }
339
+
340
+ // src/render-cms-page.tsx
341
+ var import_jsx_runtime = require("react/jsx-runtime");
342
+ var DEFAULT_TEMPLATE = `<!DOCTYPE html>
343
+ <html lang="en">
344
+ <head>
345
+ <meta charset="UTF-8">
346
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
347
+ <title>__PAGE_TITLE__</title>
348
+ <style>
349
+ * { margin: 0; padding: 0; box-sizing: border-box; }
350
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }
351
+ </style>
352
+ __THEME_CSS__
353
+ </head>
354
+ <body>
355
+ <main>
356
+ __REGION_MAIN__
357
+ </main>
358
+ </body>
359
+ </html>`;
360
+ function extractBodyContent(html) {
361
+ const bodyMatch = html.match(/<body[^>]*>([\s\S]*)<\/body>/i);
362
+ return bodyMatch?.[1] ? bodyMatch[1].trim() : html;
363
+ }
364
+ function collectSharedSectionIds(nodes) {
365
+ const ids = [];
366
+ for (const node of nodes) {
367
+ if ("mode" in node && node.mode === "shared" && node.sharedSectionId) {
368
+ ids.push(node.sharedSectionId);
369
+ }
370
+ if ("type" in node && node.type === "grid-container" && Array.isArray(node.columns)) {
371
+ for (const column of node.columns) {
372
+ if (Array.isArray(column.sections)) ids.push(...collectSharedSectionIds(column.sections));
373
+ }
374
+ }
375
+ }
376
+ return ids;
377
+ }
378
+ function prepareTemplate(templateHtml, pageData, templateCss, locale) {
379
+ let prepared = templateHtml.replace(/\{\{page_title\}\}/g, pageData.name).replace(/\{\{page_lang\}\}/g, locale).replace(/\{\{page_description\}\}/g, "");
380
+ prepared = prepared.replace(/\{\{\{theme_css\}\}\}/g, templateCss ? `<style>${templateCss}</style>` : "");
381
+ prepared = prepared.replace(/\{\{\{([A-Z_]+)\}\}\}/g, "__REGION_$1__");
382
+ prepared = prepared.replace(/\{\{#if\s+([A-Z_]+)\}\}/g, "");
383
+ prepared = prepared.replace(/\{\{\/if\}\}/g, "");
384
+ return extractBodyContent(prepared);
385
+ }
386
+ function mergeTemplateLayout(pageData) {
387
+ const merged = {};
388
+ const regions = /* @__PURE__ */ new Set([...Object.keys(pageData.layout), ...Object.keys(pageData.page_template?.layout ?? {})]);
389
+ for (const region of regions) {
390
+ merged[region] = [...pageData.page_template?.layout?.[region] ?? [], ...pageData.layout[region] ?? []];
391
+ }
392
+ return merged;
393
+ }
394
+ var errorBox = (heading, message) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", minHeight: "100vh" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { textAlign: "center" }, children: [
395
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { style: { fontSize: "1.5rem", fontWeight: 700, marginBottom: "0.5rem" }, children: heading }),
396
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { opacity: 0.7 }, children: message })
397
+ ] }) });
398
+ async function renderCmsPage({
399
+ pageData,
400
+ scope,
401
+ componentRegistry,
402
+ mode = "published",
403
+ locale = scope.locale,
404
+ environment = environmentFromEnv(),
405
+ client,
406
+ fetchSharedSection,
407
+ showModeBadge = false,
408
+ renderError
409
+ }) {
410
+ let templateHtml;
411
+ let templateCss = "";
412
+ if (pageData.page_template?.template_html) {
413
+ templateHtml = pageData.page_template.template_html;
414
+ templateCss = pageData.page_template.template_css || "";
415
+ } else if (pageData.theme?.default_template_html) {
416
+ templateHtml = pageData.theme.default_template_html;
417
+ templateCss = pageData.theme.default_template_css || "";
418
+ } else {
419
+ templateHtml = DEFAULT_TEMPLATE;
420
+ }
421
+ const preparedTemplate = prepareTemplate(templateHtml, pageData, templateCss, locale);
422
+ const pageScope = {
423
+ id: pageData.id,
424
+ kind: "page",
425
+ name: pageData.name,
426
+ slug: pageData.slug || "",
427
+ breadcrumbs: pageData.breadcrumbs || []
428
+ };
429
+ const visibilityContext = {
430
+ pageId: pageData.id,
431
+ currentPath: "/" + (pageData.slug || ""),
432
+ locale,
433
+ environment
434
+ };
435
+ const filteredLayout = {};
436
+ for (const [region, sections] of Object.entries(mergeTemplateLayout(pageData))) {
437
+ filteredLayout[region] = (0, import_react3.filterVisibleNodesRecursive)(sections, visibilityContext);
438
+ }
439
+ const fetchShared = fetchSharedSection ?? (client ? (id) => client.getPageBySlug(id, locale) : void 0);
440
+ const sharedSectionIds = Object.values(filteredLayout).flatMap(collectSharedSectionIds);
441
+ const sharedSectionsMap = /* @__PURE__ */ new Map();
442
+ if (sharedSectionIds.length > 0) {
443
+ if (!fetchShared) {
444
+ throw new Error("@botlr/next: renderCmsPage needs `client` or `fetchSharedSection` to render shared sections");
445
+ }
446
+ const results = await Promise.all(
447
+ [...new Set(sharedSectionIds)].map(async (id) => {
448
+ try {
449
+ const data = await fetchShared(id);
450
+ const main = data?.layout?.["main"];
451
+ if (Array.isArray(main) && main.length > 0) return { id, sections: main };
452
+ } catch (err) {
453
+ console.error(`Error fetching shared section ${id}:`, err);
454
+ }
455
+ return null;
456
+ })
457
+ );
458
+ for (const result of results) {
459
+ if (result) sharedSectionsMap.set(result.id, result.sections);
460
+ }
461
+ }
462
+ const parseOptions = {
463
+ replace: (domNode) => {
464
+ if (!("data" in domNode) || typeof domNode.data !== "string") return;
465
+ const regionMatch = domNode.data.match(/__REGION_([A-Z_]+)__/);
466
+ if (!regionMatch?.[1]) return;
467
+ const regionKey = regionMatch[1].toLowerCase();
468
+ const regionSections = filteredLayout[regionKey] ?? [];
469
+ if (regionSections.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, {});
470
+ const templateSectionKeys = new Set(
471
+ (pageData.page_template?.layout?.[regionKey] ?? []).map((section) => section._key)
472
+ );
473
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: regionSections.map((section) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
474
+ import_react3.SectionRenderer,
475
+ {
476
+ section,
477
+ componentRegistry,
478
+ mode,
479
+ sharedSectionsData: sharedSectionsMap,
480
+ scope,
481
+ pageScope,
482
+ fromTemplate: templateSectionKeys.has(section._key)
483
+ },
484
+ section._key
485
+ )) });
486
+ }
487
+ };
488
+ const themeSettingsCss = themeSettingsToCss(pageData.theme?.settings);
489
+ try {
490
+ const renderedContent = (0, import_html_react_parser.default)(preparedTemplate, parseOptions);
491
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
492
+ themeSettingsCss && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { dangerouslySetInnerHTML: { __html: themeSettingsCss } }),
493
+ templateCss && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { dangerouslySetInnerHTML: { __html: templateCss } }),
494
+ renderedContent,
495
+ showModeBadge && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
496
+ "div",
497
+ {
498
+ style: {
499
+ position: "fixed",
500
+ bottom: "1rem",
501
+ right: "1rem",
502
+ padding: "0.5rem 1rem",
503
+ borderRadius: "0.5rem",
504
+ background: "#111",
505
+ color: "#fff",
506
+ fontSize: "0.875rem",
507
+ fontWeight: 500,
508
+ boxShadow: "0 4px 12px rgba(0,0,0,0.2)",
509
+ zIndex: 50
510
+ },
511
+ children: mode === "draft" ? "Draft preview" : "Published preview"
512
+ }
513
+ )
514
+ ] });
515
+ } catch (error) {
516
+ console.error("Error rendering template:", error);
517
+ if (renderError) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: renderError(error) });
518
+ return errorBox("Template Error", error instanceof Error ? error.message : "Failed to render template");
519
+ }
520
+ }
521
+
522
+ // src/static-text-file.ts
523
+ var import_react4 = require("@botlr/react");
524
+ function collectSections(nodes, sectionType) {
525
+ const result = [];
526
+ for (const node of nodes) {
527
+ if ("mode" in node && node.mode === "inline" && node.sectionType === sectionType) {
528
+ result.push(node);
529
+ } else if ("type" in node && node.type === "grid-container" && Array.isArray(node.columns)) {
530
+ for (const column of node.columns) {
531
+ result.push(...collectSections(column.sections ?? [], sectionType));
532
+ }
533
+ }
534
+ }
535
+ return result;
536
+ }
537
+ var TEXT_PLAIN = { "Content-Type": "text/plain; charset=utf-8" };
538
+ async function serveStaticTextFile(client, slug, defaultContent, options = {}) {
539
+ const locale = options.locale ?? client.config.locale ?? "en";
540
+ const environment = options.environment ?? environmentFromEnv();
541
+ const sectionType = options.sectionType ?? "text";
542
+ const propKeys = options.propKeys ?? ["content", "body", "text"];
543
+ try {
544
+ const page = await client.getPageBySlug(slug, locale);
545
+ if (page?.layout) {
546
+ const visibilityContext = { locale, environment };
547
+ const sections = [];
548
+ for (const region of Object.values(page.layout)) {
549
+ if (Array.isArray(region)) {
550
+ sections.push(...collectSections((0, import_react4.filterVisibleNodesRecursive)(region, visibilityContext), sectionType));
551
+ }
552
+ }
553
+ const content = sections.map((section) => {
554
+ const props = "props" in section ? section.props ?? {} : {};
555
+ const value = propKeys.map((key) => props[key]).find((v) => typeof v === "string" && v);
556
+ return typeof value === "string" ? value : "";
557
+ }).filter(Boolean).join("\n\n");
558
+ if (content) return new Response(content, { headers: TEXT_PLAIN });
559
+ }
560
+ } catch {
561
+ }
562
+ return new Response(defaultContent, { headers: TEXT_PLAIN });
563
+ }
564
+ function defaultRobotsContent(environment = environmentFromEnv()) {
565
+ return environment === "production" ? "User-agent: *\nAllow: /\n" : "User-agent: *\nDisallow: /\n";
566
+ }
567
+
568
+ // src/locale-page.tsx
569
+ var import_navigation = require("next/navigation");
570
+ var import_jsx_runtime2 = require("react/jsx-runtime");
571
+ async function pageSlugFromParams(params) {
572
+ const slug = (await params).slug ?? [];
573
+ return slug.length > 0 ? slug.join("/") : "index";
574
+ }
575
+ function createLocalePage(config) {
576
+ const { locale, client, componentRegistry } = config;
577
+ const folder = config.folder ? `/${config.folder.replace(/^\/+|\/+$/g, "")}` : "";
578
+ const scope = scopeFromEnv({ locale, localePrefix: folder.replace(/^\//, ""), ...config.scope });
579
+ const resolvePage2 = createPageResolver(client, config.resolveOptions);
580
+ const sendRedirect = config.redirect === "temporary" ? import_navigation.redirect : import_navigation.permanentRedirect;
581
+ async function generateStaticParams() {
582
+ const response = await client.getPageSlugList({ locale, limit: config.staticParamsLimit ?? 1e3 });
583
+ return (response.data ?? []).map((path) => ({ slug: path ? path.split("/") : [] }));
584
+ }
585
+ async function generateMetadata({ params }) {
586
+ const resolved = await resolvePage2(await pageSlugFromParams(params), locale);
587
+ if (resolved.kind !== "page") return {};
588
+ return buildPageMetadata(resolved.pageData, { siteUrl: config.siteUrl, folder });
589
+ }
590
+ async function Page({ params }) {
591
+ const resolved = await resolvePage2(await pageSlugFromParams(params), locale);
592
+ if (resolved.kind === "error") {
593
+ if (config.renderError) return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: config.renderError(resolved.message) });
594
+ return errorBox("Error Loading Page", resolved.message);
595
+ }
596
+ if (resolved.kind === "404") (0, import_navigation.notFound)();
597
+ if (resolved.kind === "redirect") sendRedirect(`${folder}${resolved.destination}`);
598
+ const { pageData } = resolved;
599
+ if (pageData.status !== "published") (0, import_navigation.notFound)();
600
+ return renderCmsPage({
601
+ pageData,
602
+ scope,
603
+ componentRegistry,
604
+ mode: "published",
605
+ locale,
606
+ environment: config.environment ?? environmentFromEnv(),
607
+ client
608
+ });
609
+ }
610
+ return { generateStaticParams, generateMetadata, Page, resolvePage: resolvePage2, scope };
611
+ }
612
+ function createLocaleAliasPage(targetFolder) {
613
+ return async function AliasPage({ params }) {
614
+ const slug = (await params).slug ?? [];
615
+ (0, import_navigation.permanentRedirect)(`${targetFolder}/${slug.join("/")}`);
616
+ };
617
+ }
618
+
619
+ // src/rate-limit.ts
620
+ var store = /* @__PURE__ */ new Map();
621
+ function rateLimit(key, config) {
622
+ const now = Date.now();
623
+ const windowMs = config.window * 1e3;
624
+ let entry = store.get(key);
625
+ if (!entry || now >= entry.resetAt) {
626
+ entry = { count: 1, resetAt: now + windowMs };
627
+ store.set(key, entry);
628
+ return { allowed: true, remaining: config.limit - 1, resetAt: entry.resetAt };
629
+ }
630
+ entry.count++;
631
+ const allowed = entry.count <= config.limit;
632
+ return {
633
+ allowed,
634
+ remaining: Math.max(0, config.limit - entry.count),
635
+ resetAt: entry.resetAt
636
+ };
637
+ }
638
+ function getClientIp(request) {
639
+ return request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || request.headers.get("x-real-ip") || "unknown";
640
+ }
641
+
642
+ // src/index.ts
643
+ __reExport(index_exports, require("@botlr/react"), module.exports);
644
+ var import_sdk2 = require("@botlr/sdk");
645
+ // Annotate the CommonJS export names for ESM import in node:
646
+ 0 && (module.exports = {
647
+ BOTLR_ENV,
648
+ BotlrConflictError,
649
+ BotlrError,
650
+ DEFAULT_API_URL,
651
+ buildPageMetadata,
652
+ collectSharedSectionIds,
653
+ configFromEnv,
654
+ createAssetUrl,
655
+ createBotlrClient,
656
+ createBotlrFromEnv,
657
+ createLocaleAliasPage,
658
+ createLocalePage,
659
+ createPageResolver,
660
+ defaultRobotsContent,
661
+ environmentFromEnv,
662
+ getAssetFilename,
663
+ getAssetId,
664
+ getAssetUrl,
665
+ getBotlrForRoute,
666
+ getCachedBotlr,
667
+ getClientIp,
668
+ isAssetData,
669
+ localeFromAcceptLanguage,
670
+ mergeTemplateLayout,
671
+ pageSlugFromParams,
672
+ prepareTemplate,
673
+ rateLimit,
674
+ renderCmsPage,
675
+ resolvePage,
676
+ scopeFromEnv,
677
+ serveStaticTextFile,
678
+ themeSettingsToCss,
679
+ ...require("@botlr/react")
680
+ });
681
+ //# sourceMappingURL=index.cjs.map