@botlr/next 0.2.0 → 0.3.1
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 +21 -0
- package/README.md +6 -0
- package/dist/index.cjs +12 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -2
- package/dist/index.d.ts +12 -2
- package/dist/index.js +12 -3
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# @botlr/next
|
|
2
2
|
|
|
3
|
+
## 0.3.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies
|
|
8
|
+
- @botlr/sdk@0.4.0
|
|
9
|
+
- @botlr/react@0.1.3
|
|
10
|
+
|
|
11
|
+
## 0.3.0
|
|
12
|
+
|
|
13
|
+
### Minor Changes
|
|
14
|
+
|
|
15
|
+
- e3ff8e7: `createLocalePage` now throws when a page cannot be resolved, so `next build` fails instead of saving an error page. In development the error box is still rendered. The new `resolveErrors` option (`"throw"` or `"render"`) overrides the default; `ResolvedPage`'s error result carries the original error as `cause`.
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- e3ff8e7: Retry failed reads. GET requests that fail with a network error or a 408, 429 or 5xx status are retried twice with exponential backoff and `Retry-After` support before `BotlrError` is thrown. Configure or disable with the new `retry` client option, also accepted by `getBotlrForRoute` and `createBotlrFromEnv`. Writes are never retried.
|
|
20
|
+
- Updated dependencies [e3ff8e7]
|
|
21
|
+
- @botlr/sdk@0.3.0
|
|
22
|
+
- @botlr/react@0.1.2
|
|
23
|
+
|
|
3
24
|
## 0.2.0
|
|
4
25
|
|
|
5
26
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -57,6 +57,12 @@ for missing and unpublished pages, follows CMS redirects with
|
|
|
57
57
|
`folder`, and renders with `renderCmsPage`. `createLocaleAliasPage("/sv")`
|
|
58
58
|
makes a folder that only redirects to another one.
|
|
59
59
|
|
|
60
|
+
When resolving a page throws, even after the client's retries, the page
|
|
61
|
+
throws too: `next build` fails instead of saving an error page, and Vercel
|
|
62
|
+
keeps the previous deploy live. In development an error box is rendered in
|
|
63
|
+
its place. Pass `resolveErrors: "render"` to always show the box (with
|
|
64
|
+
`renderError` to customise it) or `resolveErrors: "throw"` to always throw.
|
|
65
|
+
|
|
60
66
|
## The pieces
|
|
61
67
|
|
|
62
68
|
- `resolvePage(client, slug, locale, options)` folds `/api/resolve` into
|
package/dist/index.cjs
CHANGED
|
@@ -132,7 +132,8 @@ async function getBotlrForRoute(params = {}) {
|
|
|
132
132
|
previewToken: params.previewToken,
|
|
133
133
|
locale,
|
|
134
134
|
preview: params.preview,
|
|
135
|
-
fetch: params.fetch
|
|
135
|
+
fetch: params.fetch,
|
|
136
|
+
retry: params.retry
|
|
136
137
|
})
|
|
137
138
|
);
|
|
138
139
|
}
|
|
@@ -190,7 +191,8 @@ async function resolvePage(client, slug, locale, options = {}) {
|
|
|
190
191
|
} catch (error) {
|
|
191
192
|
return {
|
|
192
193
|
kind: "error",
|
|
193
|
-
message: error instanceof Error ? error.message : "Unknown error"
|
|
194
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
195
|
+
cause: error
|
|
194
196
|
};
|
|
195
197
|
}
|
|
196
198
|
}
|
|
@@ -588,8 +590,15 @@ function createLocalePage(config) {
|
|
|
588
590
|
return buildPageMetadata(resolved.pageData, { siteUrl: config.siteUrl, folder });
|
|
589
591
|
}
|
|
590
592
|
async function Page({ params }) {
|
|
591
|
-
const
|
|
593
|
+
const slug = await pageSlugFromParams(params);
|
|
594
|
+
const resolved = await resolvePage2(slug, locale);
|
|
592
595
|
if (resolved.kind === "error") {
|
|
596
|
+
const mode = config.resolveErrors ?? (process.env.NODE_ENV === "development" ? "render" : "throw");
|
|
597
|
+
if (mode === "throw") {
|
|
598
|
+
throw new Error(`@botlr/next: could not resolve "${slug}" (${locale}): ${resolved.message}`, {
|
|
599
|
+
cause: resolved.cause
|
|
600
|
+
});
|
|
601
|
+
}
|
|
593
602
|
if (config.renderError) return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: config.renderError(resolved.message) });
|
|
594
603
|
return errorBox("Error Loading Page", resolved.message);
|
|
595
604
|
}
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/server.ts","../src/env.ts","../src/locale.ts","../src/resolve-page.ts","../src/page-metadata.ts","../src/render-cms-page.tsx","../src/theme-css.ts","../src/static-text-file.ts","../src/locale-page.tsx","../src/rate-limit.ts"],"sourcesContent":["export { getBotlrForRoute, getCachedBotlr, createBotlrFromEnv, type GetBotlrParams } from \"./server\";\nexport { configFromEnv, scopeFromEnv, environmentFromEnv, BOTLR_ENV, DEFAULT_API_URL } from \"./env\";\nexport { localeFromAcceptLanguage } from \"./locale\";\nexport { resolvePage, createPageResolver, type ResolvedPage, type ResolvePageOptions } from \"./resolve-page\";\nexport { buildPageMetadata, type PageMetadataOptions } from \"./page-metadata\";\nexport {\n renderCmsPage,\n prepareTemplate,\n mergeTemplateLayout,\n collectSharedSectionIds,\n type RenderCmsPageOptions,\n} from \"./render-cms-page\";\nexport { themeSettingsToCss, type ThemeSettings, type ThemeColorSet } from \"./theme-css\";\nexport { serveStaticTextFile, defaultRobotsContent, type StaticTextFileOptions } from \"./static-text-file\";\nexport {\n createLocalePage,\n createLocaleAliasPage,\n pageSlugFromParams,\n type LocalePageConfig,\n type LocalePageProps,\n} from \"./locale-page\";\nexport { rateLimit, getClientIp, type RateLimitConfig, type RateLimitResult } from \"./rate-limit\";\n\nexport * from \"@botlr/react\";\nexport type * from \"@botlr/sdk\";\nexport {\n BotlrError,\n BotlrConflictError,\n createBotlrClient,\n getAssetUrl,\n createAssetUrl,\n getAssetId,\n getAssetFilename,\n isAssetData,\n} from \"@botlr/sdk\";\n","import { headers } from \"next/headers\";\nimport { cache } from \"react\";\nimport { createBotlrClient, type BotlrClient } from \"@botlr/sdk\";\nimport { configFromEnv } from \"./env\";\nimport { localeFromAcceptLanguage } from \"./locale\";\n\nexport type GetBotlrParams = {\n /** The app instance ID. Falls back to `BOTLR_APP_ID`. */\n appId?: string;\n /** Locale override; defaults to the request's `Accept-Language`, then `en`. */\n locale?: string;\n preview?: boolean;\n /** Site token; falls back to `BOTLR_API_KEY`. Omit in dev mode. */\n apiKey?: string;\n /** Preview JWT, forwarded to `/api/resolve` in preview mode. */\n previewToken?: string;\n /** API origin; falls back to `BOTLR_API_URL`. */\n baseUrl?: string;\n fetch?: typeof fetch;\n};\n\n/**\n * A Botlr client for the current request, for Server Components, route\n * handlers and server actions. Reads the locale from `Accept-Language`\n * unless given.\n *\n * ```ts\n * const botlr = await getBotlrForRoute({ appId: scope.appInstanceId, locale: scope.locale, preview });\n * const pages = await botlr.getPageList({ tags: [\"news\"] });\n * ```\n */\nexport async function getBotlrForRoute(params: GetBotlrParams = {}): Promise<BotlrClient> {\n const h = await headers();\n const locale = params.locale || localeFromAcceptLanguage(h.get(\"accept-language\"));\n return createBotlrClient(\n configFromEnv({\n appId: params.appId,\n apiKey: params.apiKey,\n baseUrl: params.baseUrl,\n previewToken: params.previewToken,\n locale,\n preview: params.preview,\n fetch: params.fetch,\n }),\n );\n}\n\n/**\n * `getBotlrForRoute` memoised per request with React `cache`, so several\n * components in one render share a client.\n */\nexport const getCachedBotlr: (params?: GetBotlrParams) => Promise<BotlrClient> = cache(getBotlrForRoute);\n\n/**\n * A client from the environment alone, with no request context. For scripts,\n * `generateStaticParams`, instrumentation and anywhere `headers()` is not\n * available.\n */\nexport function createBotlrFromEnv(overrides: Partial<GetBotlrParams> = {}): BotlrClient {\n return createBotlrClient(configFromEnv(overrides));\n}\n","import type { BotlrClientConfig, PublisherScope } from \"@botlr/sdk\";\n\n/** Environment variables the helpers read. All server-side: the API URL is never exposed to the browser. */\nexport const BOTLR_ENV = {\n apiUrl: \"BOTLR_API_URL\",\n apiKey: \"BOTLR_API_KEY\",\n appId: \"BOTLR_APP_ID\",\n pat: \"BOTLR_PAT\",\n orgSlug: \"BOTLR_ORG_SLUG\",\n projectSlug: \"BOTLR_PROJECT_SLUG\",\n appInstanceSlug: \"BOTLR_APP_INSTANCE_SLUG\",\n environment: \"BOTLR_ENVIRONMENT\",\n} as const;\n\nexport const DEFAULT_API_URL = \"http://localhost:8888\";\n\nfunction env(name: string): string | undefined {\n const value = typeof process !== \"undefined\" ? process.env?.[name] : undefined;\n return value === \"\" ? undefined : value;\n}\n\n/**\n * Client config from the environment, with `overrides` winning. Throws when\n * no app id is known, since every Botlr request needs one.\n */\nexport function configFromEnv(overrides: Partial<BotlrClientConfig> = {}): BotlrClientConfig {\n const appId = overrides.appId ?? env(BOTLR_ENV.appId);\n if (!appId) {\n throw new Error(`@botlr/next: pass appId or set ${BOTLR_ENV.appId}`);\n }\n return {\n ...overrides,\n baseUrl: overrides.baseUrl ?? env(BOTLR_ENV.apiUrl) ?? DEFAULT_API_URL,\n appId,\n apiKey: overrides.apiKey ?? env(BOTLR_ENV.apiKey),\n personalToken: overrides.personalToken ?? env(BOTLR_ENV.pat),\n };\n}\n\n/**\n * The deployment environment used by visibility rules: `BOTLR_ENVIRONMENT`,\n * then `ENVIRONMENT`, then `NODE_ENV`, then `fallback`.\n */\nexport function environmentFromEnv(fallback = \"production\"): string {\n return env(BOTLR_ENV.environment) ?? env(\"ENVIRONMENT\") ?? env(\"NODE_ENV\") ?? fallback;\n}\n\n/**\n * The `PublisherScope` for a locale from `BOTLR_ORG_SLUG`, `BOTLR_PROJECT_SLUG`,\n * `BOTLR_APP_INSTANCE_SLUG` and `BOTLR_APP_ID`, with `overrides` winning.\n * Missing slugs become empty strings rather than errors, since only asset\n * URLs need them.\n */\nexport function scopeFromEnv(overrides: Partial<PublisherScope> & Pick<PublisherScope, \"locale\">): PublisherScope {\n return {\n orgSlug: env(BOTLR_ENV.orgSlug) ?? \"\",\n projectSlug: env(BOTLR_ENV.projectSlug) ?? \"\",\n appInstanceSlug: env(BOTLR_ENV.appInstanceSlug) ?? \"\",\n appInstanceId: env(BOTLR_ENV.appId) ?? \"\",\n ...overrides,\n };\n}\n","/** The first language of an `Accept-Language` value, or `fallback`. */\nexport function localeFromAcceptLanguage(header: string | null | undefined, fallback = \"en\"): string {\n if (!header) return fallback;\n const first = header.split(\",\")[0]?.split(\";\")[0]?.trim();\n return first || fallback;\n}\n","import { cache } from \"react\";\nimport type { BotlrClient, InlineSectionInstance, PageResponse } from \"@botlr/sdk\";\n\nexport type ResolvedPage =\n | { kind: \"page\"; pageData: PageResponse }\n | { kind: \"redirect\"; destination: string }\n | { kind: \"404\" }\n | { kind: \"error\"; message: string };\n\nexport interface ResolvePageOptions {\n /**\n * Non-page content types rendered through a section: the resolved content\n * is prepended to the `main` region as an inline section of the mapped\n * type. Defaults to `{ article: \"article\" }`.\n */\n contentSections?: Record<string, string>;\n}\n\nconst DEFAULT_CONTENT_SECTIONS: Record<string, string> = { article: \"article\" };\n\n/**\n * Resolves a slug to a page through `/api/resolve`, folding redirects, 404s\n * and errors into one result and injecting article content into the layout.\n * Not cached; see `createPageResolver`.\n */\nexport async function resolvePage(\n client: BotlrClient,\n slug: string,\n locale?: string,\n options: ResolvePageOptions = {},\n): Promise<ResolvedPage> {\n try {\n const result = await client.resolveUrl(slug, locale);\n\n if (result.kind === \"404\") return { kind: \"404\" };\n if (result.kind === \"redirect\" && result.redirect) {\n return { kind: \"redirect\", destination: result.redirect.destination };\n }\n if (!result.data) return { kind: \"404\" };\n\n const data = result.data;\n const layout = data.layout ?? {};\n\n const content = data.content ?? {};\n const contentType: string | null = content[\"type\"] ?? data.type ?? null;\n if (contentType && contentType !== \"page\") {\n const sectionType = (options.contentSections ?? DEFAULT_CONTENT_SECTIONS)[contentType];\n if (sectionType) {\n const section: InlineSectionInstance = {\n _key: `${sectionType}-${content[\"id\"] ?? data.id}`,\n mode: \"inline\",\n sectionType,\n props: content,\n status: \"published\",\n };\n layout[\"main\"] = [section, ...(layout[\"main\"] ?? [])];\n } else {\n console.warn(\"Unknown content type:\", contentType);\n }\n }\n\n return {\n kind: \"page\",\n pageData: {\n id: data.id,\n name: data.name,\n slug: data.slug || \"\",\n full_slug: data.full_slug || \"\",\n layout,\n status: data.status,\n theme: data.theme,\n page_template: data.page_template,\n seo_meta: data.seo_meta,\n alternate_translations: result.alternate_translations ?? [],\n breadcrumbs: result.breadcrumbs ?? [],\n },\n };\n } catch (error) {\n return {\n kind: \"error\",\n message: error instanceof Error ? error.message : \"Unknown error\",\n };\n }\n}\n\n/**\n * `resolvePage` bound to a client and memoised per request with React\n * `cache`, so `generateMetadata` and the page share one fetch.\n *\n * ```ts\n * const resolvePage = createPageResolver(botlr);\n * const resolved = await resolvePage(\"about/team\", \"en\");\n * ```\n */\nexport function createPageResolver(\n client: BotlrClient,\n options: ResolvePageOptions = {},\n): (slug: string, locale?: string) => Promise<ResolvedPage> {\n return cache((slug: string, locale?: string) => resolvePage(client, slug, locale, options));\n}\n","import type { Metadata } from \"next\";\nimport type { PageResponse } from \"@botlr/sdk\";\n\nexport interface PageMetadataOptions {\n /**\n * The site origin, for example `https://www.example.com`. Needed for the\n * canonical URL and hreflang alternates; without it only an explicit\n * `seo_meta.canonical` is emitted.\n */\n siteUrl?: string;\n /** URL prefix of the page's locale, for example `/en`. Empty for the root locale. */\n folder?: string;\n}\n\n/**\n * Next.js `Metadata` from a page's `seo_meta`: title, description, keywords,\n * Open Graph, Twitter, robots, canonical and hreflang alternates.\n */\nexport function buildPageMetadata(pageData: PageResponse, options: PageMetadataOptions = {}): Metadata {\n const seo = pageData.seo_meta || {};\n const title = seo.title || pageData.name;\n const alternates = pageData.alternate_translations || [];\n const siteUrl = options.siteUrl?.replace(/\\/+$/, \"\");\n const folder = options.folder ? `/${options.folder.replace(/^\\/+|\\/+$/g, \"\")}` : \"\";\n\n const metadata: Metadata = { title };\n\n if (seo.description) metadata.description = seo.description;\n if (seo.keywords) metadata.keywords = seo.keywords;\n\n const ogImage = seo.ogImage?.url || seo.ogImage?.filename;\n const description = seo.ogDescription || seo.description;\n metadata.openGraph = {\n title: seo.ogTitle || title,\n ...(description ? { description } : {}),\n ...(seo.ogType ? { type: seo.ogType } : {}),\n ...(ogImage ? { images: [ogImage] } : {}),\n };\n\n if (seo.twitterCard) {\n metadata.twitter = {\n card: seo.twitterCard,\n title: seo.ogTitle || title,\n ...(description ? { description } : {}),\n ...(ogImage ? { images: [ogImage] } : {}),\n };\n }\n\n if (seo.noindex !== undefined || seo.nofollow !== undefined || seo.robots) {\n metadata.robots = seo.robots ?? { index: !seo.noindex, follow: !seo.nofollow };\n }\n\n if (seo.canonical) {\n metadata.alternates = { canonical: seo.canonical };\n } else if (siteUrl) {\n const canonical = pageData.full_slug\n ? `${siteUrl}${folder}/${pageData.full_slug}`\n : `${siteUrl}${folder}`;\n metadata.alternates = { canonical };\n }\n\n if (siteUrl && alternates.length > 0) {\n const languages: Record<string, string> = {};\n for (const alt of alternates) {\n languages[alt.locale] = `${siteUrl}${alt.full_slug}`;\n }\n metadata.alternates = { ...metadata.alternates, languages };\n }\n\n return metadata;\n}\n","import type { JSX, ReactNode } from \"react\";\nimport parse, { type HTMLReactParserOptions } from \"html-react-parser\";\nimport {\n SectionRenderer,\n filterVisibleNodesRecursive,\n type ComponentRegistry,\n type VisibilityContext,\n} from \"@botlr/react\";\nimport type { BotlrClient, LayoutNode, PageResponse, PageScope, PublisherScope } from \"@botlr/sdk\";\nimport { environmentFromEnv } from \"./env\";\nimport { themeSettingsToCss } from \"./theme-css\";\n\ntype Layout = Record<string, LayoutNode[]>;\n\nexport interface RenderCmsPageOptions {\n pageData: PageResponse;\n scope: PublisherScope;\n /** Section type to component map used for every region. */\n componentRegistry: ComponentRegistry;\n /** Defaults to `\"published\"`; `\"draft\"` also renders draft sections. */\n mode?: \"draft\" | \"published\";\n /** Defaults to `scope.locale`. */\n locale?: string;\n /** Environment for visibility rules; defaults to `environmentFromEnv()`. */\n environment?: string;\n /** Used to fetch shared sections unless `fetchSharedSection` is given. */\n client?: BotlrClient;\n /** Fetch a shared section's page by id. Defaults to `client.getPageBySlug(id, locale)`. */\n fetchSharedSection?: (id: string) => Promise<{ layout?: Partial<Layout> } | null>;\n /** Show a fixed \"draft/published preview\" badge; for preview routes only. */\n showModeBadge?: boolean;\n /** Rendered when the template cannot be parsed. */\n renderError?: (error: unknown) => ReactNode;\n}\n\nconst DEFAULT_TEMPLATE = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>__PAGE_TITLE__</title>\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }\n </style>\n __THEME_CSS__\n</head>\n<body>\n <main>\n __REGION_MAIN__\n </main>\n</body>\n</html>`;\n\nfunction extractBodyContent(html: string): string {\n const bodyMatch = html.match(/<body[^>]*>([\\s\\S]*)<\\/body>/i);\n return bodyMatch?.[1] ? bodyMatch[1].trim() : html;\n}\n\n/** Ids of every shared section in the layout, grid columns included. */\nexport function collectSharedSectionIds(nodes: LayoutNode[]): string[] {\n const ids: string[] = [];\n for (const node of nodes) {\n if (\"mode\" in node && node.mode === \"shared\" && node.sharedSectionId) {\n ids.push(node.sharedSectionId);\n }\n if (\"type\" in node && node.type === \"grid-container\" && Array.isArray(node.columns)) {\n for (const column of node.columns) {\n if (Array.isArray(column.sections)) ids.push(...collectSharedSectionIds(column.sections));\n }\n }\n }\n return ids;\n}\n\n/**\n * The template's `{{...}}` placeholders resolved: title, lang, theme css,\n * `{{{REGION}}}` markers turned into text markers for the parser, and the\n * body extracted.\n */\nexport function prepareTemplate(templateHtml: string, pageData: PageResponse, templateCss: string, locale: string): string {\n let prepared = templateHtml\n .replace(/\\{\\{page_title\\}\\}/g, pageData.name)\n .replace(/\\{\\{page_lang\\}\\}/g, locale)\n .replace(/\\{\\{page_description\\}\\}/g, \"\");\n\n prepared = prepared.replace(/\\{\\{\\{theme_css\\}\\}\\}/g, templateCss ? `<style>${templateCss}</style>` : \"\");\n prepared = prepared.replace(/\\{\\{\\{([A-Z_]+)\\}\\}\\}/g, \"__REGION_$1__\");\n prepared = prepared.replace(/\\{\\{#if\\s+([A-Z_]+)\\}\\}/g, \"\");\n prepared = prepared.replace(/\\{\\{\\/if\\}\\}/g, \"\");\n\n return extractBodyContent(prepared);\n}\n\n/** Template sections first, then the page's own, per region. */\nexport function mergeTemplateLayout(pageData: PageResponse): Layout {\n const merged: Layout = {};\n const regions = new Set([...Object.keys(pageData.layout), ...Object.keys(pageData.page_template?.layout ?? {})]);\n for (const region of regions) {\n merged[region] = [...(pageData.page_template?.layout?.[region] ?? []), ...(pageData.layout[region] ?? [])];\n }\n return merged;\n}\n\nconst errorBox = (heading: string, message: string): JSX.Element => (\n <div style={{ display: \"flex\", alignItems: \"center\", justifyContent: \"center\", minHeight: \"100vh\" }}>\n <div style={{ textAlign: \"center\" }}>\n <h1 style={{ fontSize: \"1.5rem\", fontWeight: 700, marginBottom: \"0.5rem\" }}>{heading}</h1>\n <p style={{ opacity: 0.7 }}>{message}</p>\n </div>\n </div>\n);\n\n/**\n * Renders a resolved page: picks the page template (page, then theme, then a\n * built-in default), merges template and page layouts, applies visibility\n * rules, fetches shared sections and mounts a `SectionRenderer` at every\n * `{{{REGION}}}` marker.\n */\nexport async function renderCmsPage({\n pageData,\n scope,\n componentRegistry,\n mode = \"published\",\n locale = scope.locale,\n environment = environmentFromEnv(),\n client,\n fetchSharedSection,\n showModeBadge = false,\n renderError,\n}: RenderCmsPageOptions): Promise<JSX.Element> {\n let templateHtml: string;\n let templateCss = \"\";\n\n if (pageData.page_template?.template_html) {\n templateHtml = pageData.page_template.template_html;\n templateCss = pageData.page_template.template_css || \"\";\n } else if (pageData.theme?.default_template_html) {\n templateHtml = pageData.theme.default_template_html;\n templateCss = pageData.theme.default_template_css || \"\";\n } else {\n templateHtml = DEFAULT_TEMPLATE;\n }\n\n const preparedTemplate = prepareTemplate(templateHtml, pageData, templateCss, locale);\n\n const pageScope: PageScope = {\n id: pageData.id,\n kind: \"page\",\n name: pageData.name,\n slug: pageData.slug || \"\",\n breadcrumbs: pageData.breadcrumbs || [],\n };\n\n const visibilityContext: VisibilityContext = {\n pageId: pageData.id,\n currentPath: \"/\" + (pageData.slug || \"\"),\n locale,\n environment,\n };\n const filteredLayout: Layout = {};\n for (const [region, sections] of Object.entries(mergeTemplateLayout(pageData))) {\n filteredLayout[region] = filterVisibleNodesRecursive(sections, visibilityContext);\n }\n\n const fetchShared =\n fetchSharedSection ??\n (client ? (id: string) => client.getPageBySlug(id, locale) : undefined);\n const sharedSectionIds = Object.values(filteredLayout).flatMap(collectSharedSectionIds);\n const sharedSectionsMap = new Map<string, LayoutNode[]>();\n\n if (sharedSectionIds.length > 0) {\n if (!fetchShared) {\n throw new Error(\"@botlr/next: renderCmsPage needs `client` or `fetchSharedSection` to render shared sections\");\n }\n const results = await Promise.all(\n [...new Set(sharedSectionIds)].map(async (id) => {\n try {\n const data = await fetchShared(id);\n const main = data?.layout?.[\"main\"];\n if (Array.isArray(main) && main.length > 0) return { id, sections: main };\n } catch (err) {\n console.error(`Error fetching shared section ${id}:`, err);\n }\n return null;\n }),\n );\n for (const result of results) {\n if (result) sharedSectionsMap.set(result.id, result.sections);\n }\n }\n\n const parseOptions: HTMLReactParserOptions = {\n replace: (domNode) => {\n if (!(\"data\" in domNode) || typeof domNode.data !== \"string\") return;\n const regionMatch = domNode.data.match(/__REGION_([A-Z_]+)__/);\n if (!regionMatch?.[1]) return;\n\n const regionKey = regionMatch[1].toLowerCase();\n const regionSections = filteredLayout[regionKey] ?? [];\n if (regionSections.length === 0) return <></>;\n\n const templateSectionKeys = new Set(\n (pageData.page_template?.layout?.[regionKey] ?? []).map((section) => section._key),\n );\n\n return (\n <>\n {regionSections.map((section) => (\n <SectionRenderer\n key={section._key}\n section={section}\n componentRegistry={componentRegistry}\n mode={mode}\n sharedSectionsData={sharedSectionsMap}\n scope={scope}\n pageScope={pageScope}\n fromTemplate={templateSectionKeys.has(section._key)}\n />\n ))}\n </>\n );\n },\n };\n\n const themeSettingsCss = themeSettingsToCss(pageData.theme?.settings);\n\n try {\n const renderedContent = parse(preparedTemplate, parseOptions);\n\n return (\n <>\n {themeSettingsCss && <style dangerouslySetInnerHTML={{ __html: themeSettingsCss }} />}\n {templateCss && <style dangerouslySetInnerHTML={{ __html: templateCss }} />}\n {renderedContent}\n {showModeBadge && (\n <div\n style={{\n position: \"fixed\",\n bottom: \"1rem\",\n right: \"1rem\",\n padding: \"0.5rem 1rem\",\n borderRadius: \"0.5rem\",\n background: \"#111\",\n color: \"#fff\",\n fontSize: \"0.875rem\",\n fontWeight: 500,\n boxShadow: \"0 4px 12px rgba(0,0,0,0.2)\",\n zIndex: 50,\n }}\n >\n {mode === \"draft\" ? \"Draft preview\" : \"Published preview\"}\n </div>\n )}\n </>\n );\n } catch (error) {\n console.error(\"Error rendering template:\", error);\n if (renderError) return <>{renderError(error)}</>;\n return errorBox(\"Template Error\", error instanceof Error ? error.message : \"Failed to render template\");\n }\n}\n\nexport { errorBox as renderErrorBox };\n","export interface ThemeColorSet {\n primary?: string;\n primaryForeground?: string;\n secondary?: string;\n secondaryForeground?: string;\n accent?: string;\n accentForeground?: string;\n background?: string;\n foreground?: string;\n muted?: string;\n mutedForeground?: string;\n border?: string;\n input?: string;\n ring?: string;\n destructive?: string;\n destructiveForeground?: string;\n success?: string;\n successForeground?: string;\n warning?: string;\n warningForeground?: string;\n info?: string;\n infoForeground?: string;\n}\n\nexport interface ThemeSettings {\n colors?: { light?: ThemeColorSet; dark?: ThemeColorSet };\n typography?: {\n fontFamily?: {\n heading?: string;\n body?: string;\n ui?: string;\n code?: string;\n sans?: string;\n serif?: string;\n mono?: string;\n };\n fontSize?: {\n h1?: string; h2?: string; h3?: string; h4?: string; h5?: string; h6?: string;\n xs?: string; sm?: string; base?: string; lg?: string; xl?: string;\n \"2xl\"?: string; \"3xl\"?: string; \"4xl\"?: string;\n };\n fontWeight?: { normal?: string; medium?: string; semibold?: string; bold?: string };\n lineHeight?: { tight?: string; normal?: string; relaxed?: string };\n };\n spacing?: { xs?: string; sm?: string; md?: string; lg?: string; xl?: string; \"2xl\"?: string };\n borderRadius?: { none?: string; sm?: string; md?: string; lg?: string; xl?: string; full?: string };\n shadows?: { sm?: string; md?: string; lg?: string; xl?: string };\n layout?: {\n containerMaxWidth?: string;\n sidebarWidth?: string;\n headerHeight?: string;\n footerHeight?: string;\n };\n}\n\nconst COLOR_KEYS: (keyof ThemeColorSet)[] = [\n \"primary\", \"primaryForeground\", \"secondary\", \"secondaryForeground\", \"accent\", \"accentForeground\",\n \"background\", \"foreground\", \"muted\", \"mutedForeground\", \"border\", \"input\", \"ring\",\n \"destructive\", \"destructiveForeground\", \"success\", \"successForeground\",\n \"warning\", \"warningForeground\", \"info\", \"infoForeground\",\n];\n\n/** Groups of common variables, in output order: [settings path, css prefix, keys]. */\nconst COMMON_GROUPS: [string[], string, string[]][] = [\n [[\"typography\", \"fontFamily\"], \"--font\", [\"heading\", \"body\", \"ui\", \"code\", \"sans\", \"serif\", \"mono\"]],\n [[\"typography\", \"fontSize\"], \"--font-size\", [\"h1\", \"h2\", \"h3\", \"h4\", \"h5\", \"h6\", \"xs\", \"sm\", \"base\", \"lg\", \"xl\", \"2xl\", \"3xl\", \"4xl\"]],\n [[\"typography\", \"fontWeight\"], \"--font-weight\", [\"normal\", \"medium\", \"semibold\", \"bold\"]],\n [[\"typography\", \"lineHeight\"], \"--line-height\", [\"tight\", \"normal\", \"relaxed\"]],\n [[\"spacing\"], \"--spacing\", [\"xs\", \"sm\", \"md\", \"lg\", \"xl\", \"2xl\"]],\n [[\"borderRadius\"], \"--radius\", [\"none\", \"sm\", \"md\", \"lg\", \"xl\", \"full\"]],\n [[\"shadows\"], \"--shadow\", [\"sm\", \"md\", \"lg\", \"xl\"]],\n [[\"layout\"], \"-\", [\"containerMaxWidth\", \"sidebarWidth\", \"headerHeight\", \"footerHeight\"]],\n];\n\nconst kebab = (key: string): string => key.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);\n\nfunction colorVars(colors: ThemeColorSet | undefined): string[] {\n if (!colors) return [];\n const vars: string[] = [];\n for (const key of COLOR_KEYS) {\n const value = colors[key];\n if (value) vars.push(`--color-${kebab(key)}: ${value};`);\n }\n return vars;\n}\n\nfunction pick(settings: Record<string, any>, path: string[]): Record<string, string> | undefined {\n let current: any = settings;\n for (const segment of path) {\n current = current?.[segment];\n if (!current) return undefined;\n }\n return current;\n}\n\n/**\n * Theme settings as CSS custom properties: common variables and light colors\n * on `:root`, dark colors under `prefers-color-scheme: dark` and `.dark`.\n */\nexport function themeSettingsToCss(settings?: ThemeSettings | Record<string, any>): string {\n if (!settings) return \"\";\n\n const s = settings as ThemeSettings;\n const lightVars = colorVars(s.colors?.light);\n const darkVars = colorVars(s.colors?.dark);\n const commonVars: string[] = [];\n\n for (const [path, prefix, keys] of COMMON_GROUPS) {\n const group = pick(settings, path);\n if (!group) continue;\n for (const key of keys) {\n const value = group[key];\n if (value) commonVars.push(`${prefix}-${kebab(key)}: ${value};`);\n }\n }\n\n const cssBlocks: string[] = [];\n\n if (commonVars.length > 0) {\n cssBlocks.push(`:root {\\n ${commonVars.join(\"\\n \")}\\n}`);\n }\n\n if (lightVars.length > 0) {\n cssBlocks.push(`:root {\\n ${lightVars.join(\"\\n \")}\\n}`);\n }\n\n if (darkVars.length > 0) {\n cssBlocks.push(`@media (prefers-color-scheme: dark) {\\n :root {\\n ${darkVars.join(\"\\n \")}\\n }\\n}`);\n cssBlocks.push(`.dark {\\n ${darkVars.join(\"\\n \")}\\n}`);\n }\n\n return cssBlocks.join(\"\\n\\n\");\n}\n","import { filterVisibleNodesRecursive, type VisibilityContext } from \"@botlr/react\";\nimport type { BotlrClient, LayoutNode } from \"@botlr/sdk\";\nimport { environmentFromEnv } from \"./env\";\n\nexport interface StaticTextFileOptions {\n /** Defaults to the client's locale, then `en`. */\n locale?: string;\n /** Environment for visibility rules; defaults to `environmentFromEnv()`. */\n environment?: string;\n /** The section type whose content is served; defaults to `text`. */\n sectionType?: string;\n /** Section props tried in order for the text; defaults to `content`, `body`, `text`. */\n propKeys?: string[];\n}\n\nfunction collectSections(nodes: LayoutNode[], sectionType: string): LayoutNode[] {\n const result: LayoutNode[] = [];\n for (const node of nodes) {\n if (\"mode\" in node && node.mode === \"inline\" && node.sectionType === sectionType) {\n result.push(node);\n } else if (\"type\" in node && node.type === \"grid-container\" && Array.isArray(node.columns)) {\n for (const column of node.columns) {\n result.push(...collectSections(column.sections ?? [], sectionType));\n }\n }\n }\n return result;\n}\n\nconst TEXT_PLAIN = { \"Content-Type\": \"text/plain; charset=utf-8\" };\n\n/**\n * A CMS-managed plain-text file such as `robots.txt`, `humans.txt` or\n * `llms.txt`: the page at `slug`, its visible text sections concatenated.\n * Serves `defaultContent` when the page has none or cannot be fetched.\n *\n * ```ts\n * // app/robots.txt/route.ts\n * export const GET = () => serveStaticTextFile(botlr, \"robots.txt\", defaultRobotsContent());\n * ```\n */\nexport async function serveStaticTextFile(\n client: BotlrClient,\n slug: string,\n defaultContent: string,\n options: StaticTextFileOptions = {},\n): Promise<Response> {\n const locale = options.locale ?? client.config.locale ?? \"en\";\n const environment = options.environment ?? environmentFromEnv();\n const sectionType = options.sectionType ?? \"text\";\n const propKeys = options.propKeys ?? [\"content\", \"body\", \"text\"];\n\n try {\n const page = await client.getPageBySlug(slug, locale);\n if (page?.layout) {\n const visibilityContext: VisibilityContext = { locale, environment };\n const sections: LayoutNode[] = [];\n for (const region of Object.values(page.layout)) {\n if (Array.isArray(region)) {\n sections.push(...collectSections(filterVisibleNodesRecursive(region, visibilityContext), sectionType));\n }\n }\n\n const content = sections\n .map((section) => {\n const props: Record<string, unknown> = \"props\" in section ? (section.props ?? {}) : {};\n const value = propKeys.map((key) => props[key]).find((v) => typeof v === \"string\" && v);\n return typeof value === \"string\" ? value : \"\";\n })\n .filter(Boolean)\n .join(\"\\n\\n\");\n\n if (content) return new Response(content, { headers: TEXT_PLAIN });\n }\n } catch {\n // Fall through to the default\n }\n\n return new Response(defaultContent, { headers: TEXT_PLAIN });\n}\n\n/** Allow everything in production, disallow everything elsewhere. */\nexport function defaultRobotsContent(environment = environmentFromEnv()): string {\n return environment === \"production\" ? \"User-agent: *\\nAllow: /\\n\" : \"User-agent: *\\nDisallow: /\\n\";\n}\n","import type { Metadata } from \"next\";\nimport { notFound, permanentRedirect, redirect } from \"next/navigation\";\nimport type { ReactNode } from \"react\";\nimport type { ComponentRegistry } from \"@botlr/react\";\nimport type { BotlrClient, PublisherScope } from \"@botlr/sdk\";\nimport { environmentFromEnv, scopeFromEnv } from \"./env\";\nimport { buildPageMetadata } from \"./page-metadata\";\nimport { renderCmsPage, renderErrorBox } from \"./render-cms-page\";\nimport { createPageResolver, type ResolvePageOptions, type ResolvedPage } from \"./resolve-page\";\n\nexport interface LocalePageConfig {\n /** CMS locale code, for example `da` or `en`. */\n locale: string;\n /** URL prefix for this locale, for example `/en`. Empty for the root locale. */\n folder?: string;\n /** The client the pages are fetched with; usually one built with `createBotlrFromEnv`. */\n client: BotlrClient;\n componentRegistry: ComponentRegistry;\n /** Overrides on the scope built from the environment. */\n scope?: Partial<PublisherScope>;\n /** The site origin, for canonical and hreflang metadata. */\n siteUrl?: string;\n /** Environment for visibility rules; defaults to `environmentFromEnv()`. */\n environment?: string;\n resolveOptions?: ResolvePageOptions;\n /** Redirect kind for CMS redirects; defaults to `permanent` (308). */\n redirect?: \"permanent\" | \"temporary\";\n /** Page slugs fetched for `generateStaticParams`; defaults to 1000. */\n staticParamsLimit?: number;\n /** Rendered when resolving fails. */\n renderError?: (message: string) => ReactNode;\n}\n\nexport interface LocalePageProps {\n params: Promise<{ slug?: string[] }>;\n searchParams?: Promise<Record<string, string | string[] | undefined>>;\n}\n\n/** The page slug for a catch-all route: the segments joined, or `index`. */\nexport async function pageSlugFromParams(params: LocalePageProps[\"params\"]): Promise<string> {\n const slug = (await params).slug ?? [];\n return slug.length > 0 ? slug.join(\"/\") : \"index\";\n}\n\n/**\n * Everything a static per-locale `[[...slug]]` route needs. The route file\n * keeps only its segment config, which Next must read statically:\n *\n * ```ts\n * // app/(root)/[[...slug]]/page.tsx\n * export const revalidate = false;\n * export const dynamicParams = false;\n * const page = createLocalePage({ locale: \"da\", folder: \"\", client: botlr, componentRegistry });\n * export const generateStaticParams = page.generateStaticParams;\n * export const generateMetadata = page.generateMetadata;\n * export default page.Page;\n * ```\n */\nexport function createLocalePage(config: LocalePageConfig) {\n const { locale, client, componentRegistry } = config;\n const folder = config.folder ? `/${config.folder.replace(/^\\/+|\\/+$/g, \"\")}` : \"\";\n const scope: PublisherScope = scopeFromEnv({ locale, localePrefix: folder.replace(/^\\//, \"\"), ...config.scope });\n const resolvePage = createPageResolver(client, config.resolveOptions);\n const sendRedirect: (url: string) => never = config.redirect === \"temporary\" ? redirect : permanentRedirect;\n\n async function generateStaticParams(): Promise<{ slug?: string[] }[]> {\n const response = await client.getPageSlugList({ locale, limit: config.staticParamsLimit ?? 1000 });\n return (response.data ?? []).map((path) => ({ slug: path ? path.split(\"/\") : [] }));\n }\n\n async function generateMetadata({ params }: LocalePageProps): Promise<Metadata> {\n const resolved = await resolvePage(await pageSlugFromParams(params), locale);\n if (resolved.kind !== \"page\") return {};\n return buildPageMetadata(resolved.pageData, { siteUrl: config.siteUrl, folder });\n }\n\n async function Page({ params }: LocalePageProps) {\n const resolved: ResolvedPage = await resolvePage(await pageSlugFromParams(params), locale);\n\n if (resolved.kind === \"error\") {\n if (config.renderError) return <>{config.renderError(resolved.message)}</>;\n return renderErrorBox(\"Error Loading Page\", resolved.message);\n }\n if (resolved.kind === \"404\") notFound();\n if (resolved.kind === \"redirect\") sendRedirect(`${folder}${resolved.destination}`);\n\n const { pageData } = resolved;\n if (pageData.status !== \"published\") notFound();\n\n return renderCmsPage({\n pageData,\n scope,\n componentRegistry,\n mode: \"published\",\n locale,\n environment: config.environment ?? environmentFromEnv(),\n client,\n });\n }\n\n return { generateStaticParams, generateMetadata, Page, resolvePage, scope };\n}\n\n/**\n * A locale folder that only aliases another, for example `/se/*` to `/sv/*`.\n * Permanently redirects, keeping the rest of the path.\n */\nexport function createLocaleAliasPage(targetFolder: string) {\n return async function AliasPage({ params }: LocalePageProps): Promise<never> {\n const slug = (await params).slug ?? [];\n permanentRedirect(`${targetFolder}/${slug.join(\"/\")}`);\n };\n}\n","type Entry = { count: number; resetAt: number };\n\nconst store = new Map<string, Entry>();\n\nexport interface RateLimitConfig {\n /** Max requests allowed in the window */\n limit: number;\n /** Window size in seconds */\n window: number;\n}\n\nexport interface RateLimitResult {\n allowed: boolean;\n remaining: number;\n /** Epoch ms when the window resets */\n resetAt: number;\n}\n\n/**\n * A fixed-window in-memory rate limit, per server instance. Enough to slow\n * down form and forum spam; not shared across instances or restarts.\n *\n * ```ts\n * const rl = rateLimit(`form:${getClientIp(request)}`, { limit: 5, window: 60 });\n * if (!rl.allowed) return new Response(\"Too many requests\", { status: 429 });\n * ```\n */\nexport function rateLimit(key: string, config: RateLimitConfig): RateLimitResult {\n const now = Date.now();\n const windowMs = config.window * 1000;\n\n let entry = store.get(key);\n\n if (!entry || now >= entry.resetAt) {\n entry = { count: 1, resetAt: now + windowMs };\n store.set(key, entry);\n return { allowed: true, remaining: config.limit - 1, resetAt: entry.resetAt };\n }\n\n entry.count++;\n const allowed = entry.count <= config.limit;\n\n return {\n allowed,\n remaining: Math.max(0, config.limit - entry.count),\n resetAt: entry.resetAt,\n };\n}\n\n/** The client IP from `x-forwarded-for` or `x-real-ip`, or `\"unknown\"`. */\nexport function getClientIp(request: Request): string {\n return (\n request.headers.get(\"x-forwarded-for\")?.split(\",\")[0]?.trim() ||\n request.headers.get(\"x-real-ip\") ||\n \"unknown\"\n );\n}\n"],"mappings":"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","names":["import_react","import_react","parse","import_react","import_jsx_runtime","resolvePage","import_sdk"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/server.ts","../src/env.ts","../src/locale.ts","../src/resolve-page.ts","../src/page-metadata.ts","../src/render-cms-page.tsx","../src/theme-css.ts","../src/static-text-file.ts","../src/locale-page.tsx","../src/rate-limit.ts"],"sourcesContent":["export { getBotlrForRoute, getCachedBotlr, createBotlrFromEnv, type GetBotlrParams } from \"./server\";\nexport { configFromEnv, scopeFromEnv, environmentFromEnv, BOTLR_ENV, DEFAULT_API_URL } from \"./env\";\nexport { localeFromAcceptLanguage } from \"./locale\";\nexport { resolvePage, createPageResolver, type ResolvedPage, type ResolvePageOptions } from \"./resolve-page\";\nexport { buildPageMetadata, type PageMetadataOptions } from \"./page-metadata\";\nexport {\n renderCmsPage,\n prepareTemplate,\n mergeTemplateLayout,\n collectSharedSectionIds,\n type RenderCmsPageOptions,\n} from \"./render-cms-page\";\nexport { themeSettingsToCss, type ThemeSettings, type ThemeColorSet } from \"./theme-css\";\nexport { serveStaticTextFile, defaultRobotsContent, type StaticTextFileOptions } from \"./static-text-file\";\nexport {\n createLocalePage,\n createLocaleAliasPage,\n pageSlugFromParams,\n type LocalePageConfig,\n type LocalePageProps,\n} from \"./locale-page\";\nexport { rateLimit, getClientIp, type RateLimitConfig, type RateLimitResult } from \"./rate-limit\";\n\nexport * from \"@botlr/react\";\nexport type * from \"@botlr/sdk\";\nexport {\n BotlrError,\n BotlrConflictError,\n createBotlrClient,\n getAssetUrl,\n createAssetUrl,\n getAssetId,\n getAssetFilename,\n isAssetData,\n} from \"@botlr/sdk\";\n","import { headers } from \"next/headers\";\nimport { cache } from \"react\";\nimport { createBotlrClient, type BotlrClient, type BotlrClientConfig } from \"@botlr/sdk\";\nimport { configFromEnv } from \"./env\";\nimport { localeFromAcceptLanguage } from \"./locale\";\n\nexport type GetBotlrParams = {\n /** The app instance ID. Falls back to `BOTLR_APP_ID`. */\n appId?: string;\n /** Locale override; defaults to the request's `Accept-Language`, then `en`. */\n locale?: string;\n preview?: boolean;\n /** Site token; falls back to `BOTLR_API_KEY`. Omit in dev mode. */\n apiKey?: string;\n /** Preview JWT, forwarded to `/api/resolve` in preview mode. */\n previewToken?: string;\n /** API origin; falls back to `BOTLR_API_URL`. */\n baseUrl?: string;\n fetch?: typeof fetch;\n /** Retry policy for failed reads; see `BotlrClientConfig.retry`. */\n retry?: BotlrClientConfig[\"retry\"];\n};\n\n/**\n * A Botlr client for the current request, for Server Components, route\n * handlers and server actions. Reads the locale from `Accept-Language`\n * unless given.\n *\n * ```ts\n * const botlr = await getBotlrForRoute({ appId: scope.appInstanceId, locale: scope.locale, preview });\n * const pages = await botlr.getPageList({ tags: [\"news\"] });\n * ```\n */\nexport async function getBotlrForRoute(params: GetBotlrParams = {}): Promise<BotlrClient> {\n const h = await headers();\n const locale = params.locale || localeFromAcceptLanguage(h.get(\"accept-language\"));\n return createBotlrClient(\n configFromEnv({\n appId: params.appId,\n apiKey: params.apiKey,\n baseUrl: params.baseUrl,\n previewToken: params.previewToken,\n locale,\n preview: params.preview,\n fetch: params.fetch,\n retry: params.retry,\n }),\n );\n}\n\n/**\n * `getBotlrForRoute` memoised per request with React `cache`, so several\n * components in one render share a client.\n */\nexport const getCachedBotlr: (params?: GetBotlrParams) => Promise<BotlrClient> = cache(getBotlrForRoute);\n\n/**\n * A client from the environment alone, with no request context. For scripts,\n * `generateStaticParams`, instrumentation and anywhere `headers()` is not\n * available.\n */\nexport function createBotlrFromEnv(overrides: Partial<GetBotlrParams> = {}): BotlrClient {\n return createBotlrClient(configFromEnv(overrides));\n}\n","import type { BotlrClientConfig, PublisherScope } from \"@botlr/sdk\";\n\n/** Environment variables the helpers read. All server-side: the API URL is never exposed to the browser. */\nexport const BOTLR_ENV = {\n apiUrl: \"BOTLR_API_URL\",\n apiKey: \"BOTLR_API_KEY\",\n appId: \"BOTLR_APP_ID\",\n pat: \"BOTLR_PAT\",\n orgSlug: \"BOTLR_ORG_SLUG\",\n projectSlug: \"BOTLR_PROJECT_SLUG\",\n appInstanceSlug: \"BOTLR_APP_INSTANCE_SLUG\",\n environment: \"BOTLR_ENVIRONMENT\",\n} as const;\n\nexport const DEFAULT_API_URL = \"http://localhost:8888\";\n\nfunction env(name: string): string | undefined {\n const value = typeof process !== \"undefined\" ? process.env?.[name] : undefined;\n return value === \"\" ? undefined : value;\n}\n\n/**\n * Client config from the environment, with `overrides` winning. Throws when\n * no app id is known, since every Botlr request needs one.\n */\nexport function configFromEnv(overrides: Partial<BotlrClientConfig> = {}): BotlrClientConfig {\n const appId = overrides.appId ?? env(BOTLR_ENV.appId);\n if (!appId) {\n throw new Error(`@botlr/next: pass appId or set ${BOTLR_ENV.appId}`);\n }\n return {\n ...overrides,\n baseUrl: overrides.baseUrl ?? env(BOTLR_ENV.apiUrl) ?? DEFAULT_API_URL,\n appId,\n apiKey: overrides.apiKey ?? env(BOTLR_ENV.apiKey),\n personalToken: overrides.personalToken ?? env(BOTLR_ENV.pat),\n };\n}\n\n/**\n * The deployment environment used by visibility rules: `BOTLR_ENVIRONMENT`,\n * then `ENVIRONMENT`, then `NODE_ENV`, then `fallback`.\n */\nexport function environmentFromEnv(fallback = \"production\"): string {\n return env(BOTLR_ENV.environment) ?? env(\"ENVIRONMENT\") ?? env(\"NODE_ENV\") ?? fallback;\n}\n\n/**\n * The `PublisherScope` for a locale from `BOTLR_ORG_SLUG`, `BOTLR_PROJECT_SLUG`,\n * `BOTLR_APP_INSTANCE_SLUG` and `BOTLR_APP_ID`, with `overrides` winning.\n * Missing slugs become empty strings rather than errors, since only asset\n * URLs need them.\n */\nexport function scopeFromEnv(overrides: Partial<PublisherScope> & Pick<PublisherScope, \"locale\">): PublisherScope {\n return {\n orgSlug: env(BOTLR_ENV.orgSlug) ?? \"\",\n projectSlug: env(BOTLR_ENV.projectSlug) ?? \"\",\n appInstanceSlug: env(BOTLR_ENV.appInstanceSlug) ?? \"\",\n appInstanceId: env(BOTLR_ENV.appId) ?? \"\",\n ...overrides,\n };\n}\n","/** The first language of an `Accept-Language` value, or `fallback`. */\nexport function localeFromAcceptLanguage(header: string | null | undefined, fallback = \"en\"): string {\n if (!header) return fallback;\n const first = header.split(\",\")[0]?.split(\";\")[0]?.trim();\n return first || fallback;\n}\n","import { cache } from \"react\";\nimport type { BotlrClient, InlineSectionInstance, PageResponse } from \"@botlr/sdk\";\n\nexport type ResolvedPage =\n | { kind: \"page\"; pageData: PageResponse }\n | { kind: \"redirect\"; destination: string }\n | { kind: \"404\" }\n | { kind: \"error\"; message: string; cause: unknown };\n\nexport interface ResolvePageOptions {\n /**\n * Non-page content types rendered through a section: the resolved content\n * is prepended to the `main` region as an inline section of the mapped\n * type. Defaults to `{ article: \"article\" }`.\n */\n contentSections?: Record<string, string>;\n}\n\nconst DEFAULT_CONTENT_SECTIONS: Record<string, string> = { article: \"article\" };\n\n/**\n * Resolves a slug to a page through `/api/resolve`, folding redirects, 404s\n * and errors into one result and injecting article content into the layout.\n * Not cached; see `createPageResolver`.\n */\nexport async function resolvePage(\n client: BotlrClient,\n slug: string,\n locale?: string,\n options: ResolvePageOptions = {},\n): Promise<ResolvedPage> {\n try {\n const result = await client.resolveUrl(slug, locale);\n\n if (result.kind === \"404\") return { kind: \"404\" };\n if (result.kind === \"redirect\" && result.redirect) {\n return { kind: \"redirect\", destination: result.redirect.destination };\n }\n if (!result.data) return { kind: \"404\" };\n\n const data = result.data;\n const layout = data.layout ?? {};\n\n const content = data.content ?? {};\n const contentType: string | null = content[\"type\"] ?? data.type ?? null;\n if (contentType && contentType !== \"page\") {\n const sectionType = (options.contentSections ?? DEFAULT_CONTENT_SECTIONS)[contentType];\n if (sectionType) {\n const section: InlineSectionInstance = {\n _key: `${sectionType}-${content[\"id\"] ?? data.id}`,\n mode: \"inline\",\n sectionType,\n props: content,\n status: \"published\",\n };\n layout[\"main\"] = [section, ...(layout[\"main\"] ?? [])];\n } else {\n console.warn(\"Unknown content type:\", contentType);\n }\n }\n\n return {\n kind: \"page\",\n pageData: {\n id: data.id,\n name: data.name,\n slug: data.slug || \"\",\n full_slug: data.full_slug || \"\",\n layout,\n status: data.status,\n theme: data.theme,\n page_template: data.page_template,\n seo_meta: data.seo_meta,\n alternate_translations: result.alternate_translations ?? [],\n breadcrumbs: result.breadcrumbs ?? [],\n },\n };\n } catch (error) {\n return {\n kind: \"error\",\n message: error instanceof Error ? error.message : \"Unknown error\",\n cause: error,\n };\n }\n}\n\n/**\n * `resolvePage` bound to a client and memoised per request with React\n * `cache`, so `generateMetadata` and the page share one fetch.\n *\n * ```ts\n * const resolvePage = createPageResolver(botlr);\n * const resolved = await resolvePage(\"about/team\", \"en\");\n * ```\n */\nexport function createPageResolver(\n client: BotlrClient,\n options: ResolvePageOptions = {},\n): (slug: string, locale?: string) => Promise<ResolvedPage> {\n return cache((slug: string, locale?: string) => resolvePage(client, slug, locale, options));\n}\n","import type { Metadata } from \"next\";\nimport type { PageResponse } from \"@botlr/sdk\";\n\nexport interface PageMetadataOptions {\n /**\n * The site origin, for example `https://www.example.com`. Needed for the\n * canonical URL and hreflang alternates; without it only an explicit\n * `seo_meta.canonical` is emitted.\n */\n siteUrl?: string;\n /** URL prefix of the page's locale, for example `/en`. Empty for the root locale. */\n folder?: string;\n}\n\n/**\n * Next.js `Metadata` from a page's `seo_meta`: title, description, keywords,\n * Open Graph, Twitter, robots, canonical and hreflang alternates.\n */\nexport function buildPageMetadata(pageData: PageResponse, options: PageMetadataOptions = {}): Metadata {\n const seo = pageData.seo_meta || {};\n const title = seo.title || pageData.name;\n const alternates = pageData.alternate_translations || [];\n const siteUrl = options.siteUrl?.replace(/\\/+$/, \"\");\n const folder = options.folder ? `/${options.folder.replace(/^\\/+|\\/+$/g, \"\")}` : \"\";\n\n const metadata: Metadata = { title };\n\n if (seo.description) metadata.description = seo.description;\n if (seo.keywords) metadata.keywords = seo.keywords;\n\n const ogImage = seo.ogImage?.url || seo.ogImage?.filename;\n const description = seo.ogDescription || seo.description;\n metadata.openGraph = {\n title: seo.ogTitle || title,\n ...(description ? { description } : {}),\n ...(seo.ogType ? { type: seo.ogType } : {}),\n ...(ogImage ? { images: [ogImage] } : {}),\n };\n\n if (seo.twitterCard) {\n metadata.twitter = {\n card: seo.twitterCard,\n title: seo.ogTitle || title,\n ...(description ? { description } : {}),\n ...(ogImage ? { images: [ogImage] } : {}),\n };\n }\n\n if (seo.noindex !== undefined || seo.nofollow !== undefined || seo.robots) {\n metadata.robots = seo.robots ?? { index: !seo.noindex, follow: !seo.nofollow };\n }\n\n if (seo.canonical) {\n metadata.alternates = { canonical: seo.canonical };\n } else if (siteUrl) {\n const canonical = pageData.full_slug\n ? `${siteUrl}${folder}/${pageData.full_slug}`\n : `${siteUrl}${folder}`;\n metadata.alternates = { canonical };\n }\n\n if (siteUrl && alternates.length > 0) {\n const languages: Record<string, string> = {};\n for (const alt of alternates) {\n languages[alt.locale] = `${siteUrl}${alt.full_slug}`;\n }\n metadata.alternates = { ...metadata.alternates, languages };\n }\n\n return metadata;\n}\n","import type { JSX, ReactNode } from \"react\";\nimport parse, { type HTMLReactParserOptions } from \"html-react-parser\";\nimport {\n SectionRenderer,\n filterVisibleNodesRecursive,\n type ComponentRegistry,\n type VisibilityContext,\n} from \"@botlr/react\";\nimport type { BotlrClient, LayoutNode, PageResponse, PageScope, PublisherScope } from \"@botlr/sdk\";\nimport { environmentFromEnv } from \"./env\";\nimport { themeSettingsToCss } from \"./theme-css\";\n\ntype Layout = Record<string, LayoutNode[]>;\n\nexport interface RenderCmsPageOptions {\n pageData: PageResponse;\n scope: PublisherScope;\n /** Section type to component map used for every region. */\n componentRegistry: ComponentRegistry;\n /** Defaults to `\"published\"`; `\"draft\"` also renders draft sections. */\n mode?: \"draft\" | \"published\";\n /** Defaults to `scope.locale`. */\n locale?: string;\n /** Environment for visibility rules; defaults to `environmentFromEnv()`. */\n environment?: string;\n /** Used to fetch shared sections unless `fetchSharedSection` is given. */\n client?: BotlrClient;\n /** Fetch a shared section's page by id. Defaults to `client.getPageBySlug(id, locale)`. */\n fetchSharedSection?: (id: string) => Promise<{ layout?: Partial<Layout> } | null>;\n /** Show a fixed \"draft/published preview\" badge; for preview routes only. */\n showModeBadge?: boolean;\n /** Rendered when the template cannot be parsed. */\n renderError?: (error: unknown) => ReactNode;\n}\n\nconst DEFAULT_TEMPLATE = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>__PAGE_TITLE__</title>\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }\n </style>\n __THEME_CSS__\n</head>\n<body>\n <main>\n __REGION_MAIN__\n </main>\n</body>\n</html>`;\n\nfunction extractBodyContent(html: string): string {\n const bodyMatch = html.match(/<body[^>]*>([\\s\\S]*)<\\/body>/i);\n return bodyMatch?.[1] ? bodyMatch[1].trim() : html;\n}\n\n/** Ids of every shared section in the layout, grid columns included. */\nexport function collectSharedSectionIds(nodes: LayoutNode[]): string[] {\n const ids: string[] = [];\n for (const node of nodes) {\n if (\"mode\" in node && node.mode === \"shared\" && node.sharedSectionId) {\n ids.push(node.sharedSectionId);\n }\n if (\"type\" in node && node.type === \"grid-container\" && Array.isArray(node.columns)) {\n for (const column of node.columns) {\n if (Array.isArray(column.sections)) ids.push(...collectSharedSectionIds(column.sections));\n }\n }\n }\n return ids;\n}\n\n/**\n * The template's `{{...}}` placeholders resolved: title, lang, theme css,\n * `{{{REGION}}}` markers turned into text markers for the parser, and the\n * body extracted.\n */\nexport function prepareTemplate(templateHtml: string, pageData: PageResponse, templateCss: string, locale: string): string {\n let prepared = templateHtml\n .replace(/\\{\\{page_title\\}\\}/g, pageData.name)\n .replace(/\\{\\{page_lang\\}\\}/g, locale)\n .replace(/\\{\\{page_description\\}\\}/g, \"\");\n\n prepared = prepared.replace(/\\{\\{\\{theme_css\\}\\}\\}/g, templateCss ? `<style>${templateCss}</style>` : \"\");\n prepared = prepared.replace(/\\{\\{\\{([A-Z_]+)\\}\\}\\}/g, \"__REGION_$1__\");\n prepared = prepared.replace(/\\{\\{#if\\s+([A-Z_]+)\\}\\}/g, \"\");\n prepared = prepared.replace(/\\{\\{\\/if\\}\\}/g, \"\");\n\n return extractBodyContent(prepared);\n}\n\n/** Template sections first, then the page's own, per region. */\nexport function mergeTemplateLayout(pageData: PageResponse): Layout {\n const merged: Layout = {};\n const regions = new Set([...Object.keys(pageData.layout), ...Object.keys(pageData.page_template?.layout ?? {})]);\n for (const region of regions) {\n merged[region] = [...(pageData.page_template?.layout?.[region] ?? []), ...(pageData.layout[region] ?? [])];\n }\n return merged;\n}\n\nconst errorBox = (heading: string, message: string): JSX.Element => (\n <div style={{ display: \"flex\", alignItems: \"center\", justifyContent: \"center\", minHeight: \"100vh\" }}>\n <div style={{ textAlign: \"center\" }}>\n <h1 style={{ fontSize: \"1.5rem\", fontWeight: 700, marginBottom: \"0.5rem\" }}>{heading}</h1>\n <p style={{ opacity: 0.7 }}>{message}</p>\n </div>\n </div>\n);\n\n/**\n * Renders a resolved page: picks the page template (page, then theme, then a\n * built-in default), merges template and page layouts, applies visibility\n * rules, fetches shared sections and mounts a `SectionRenderer` at every\n * `{{{REGION}}}` marker.\n */\nexport async function renderCmsPage({\n pageData,\n scope,\n componentRegistry,\n mode = \"published\",\n locale = scope.locale,\n environment = environmentFromEnv(),\n client,\n fetchSharedSection,\n showModeBadge = false,\n renderError,\n}: RenderCmsPageOptions): Promise<JSX.Element> {\n let templateHtml: string;\n let templateCss = \"\";\n\n if (pageData.page_template?.template_html) {\n templateHtml = pageData.page_template.template_html;\n templateCss = pageData.page_template.template_css || \"\";\n } else if (pageData.theme?.default_template_html) {\n templateHtml = pageData.theme.default_template_html;\n templateCss = pageData.theme.default_template_css || \"\";\n } else {\n templateHtml = DEFAULT_TEMPLATE;\n }\n\n const preparedTemplate = prepareTemplate(templateHtml, pageData, templateCss, locale);\n\n const pageScope: PageScope = {\n id: pageData.id,\n kind: \"page\",\n name: pageData.name,\n slug: pageData.slug || \"\",\n breadcrumbs: pageData.breadcrumbs || [],\n };\n\n const visibilityContext: VisibilityContext = {\n pageId: pageData.id,\n currentPath: \"/\" + (pageData.slug || \"\"),\n locale,\n environment,\n };\n const filteredLayout: Layout = {};\n for (const [region, sections] of Object.entries(mergeTemplateLayout(pageData))) {\n filteredLayout[region] = filterVisibleNodesRecursive(sections, visibilityContext);\n }\n\n const fetchShared =\n fetchSharedSection ??\n (client ? (id: string) => client.getPageBySlug(id, locale) : undefined);\n const sharedSectionIds = Object.values(filteredLayout).flatMap(collectSharedSectionIds);\n const sharedSectionsMap = new Map<string, LayoutNode[]>();\n\n if (sharedSectionIds.length > 0) {\n if (!fetchShared) {\n throw new Error(\"@botlr/next: renderCmsPage needs `client` or `fetchSharedSection` to render shared sections\");\n }\n const results = await Promise.all(\n [...new Set(sharedSectionIds)].map(async (id) => {\n try {\n const data = await fetchShared(id);\n const main = data?.layout?.[\"main\"];\n if (Array.isArray(main) && main.length > 0) return { id, sections: main };\n } catch (err) {\n console.error(`Error fetching shared section ${id}:`, err);\n }\n return null;\n }),\n );\n for (const result of results) {\n if (result) sharedSectionsMap.set(result.id, result.sections);\n }\n }\n\n const parseOptions: HTMLReactParserOptions = {\n replace: (domNode) => {\n if (!(\"data\" in domNode) || typeof domNode.data !== \"string\") return;\n const regionMatch = domNode.data.match(/__REGION_([A-Z_]+)__/);\n if (!regionMatch?.[1]) return;\n\n const regionKey = regionMatch[1].toLowerCase();\n const regionSections = filteredLayout[regionKey] ?? [];\n if (regionSections.length === 0) return <></>;\n\n const templateSectionKeys = new Set(\n (pageData.page_template?.layout?.[regionKey] ?? []).map((section) => section._key),\n );\n\n return (\n <>\n {regionSections.map((section) => (\n <SectionRenderer\n key={section._key}\n section={section}\n componentRegistry={componentRegistry}\n mode={mode}\n sharedSectionsData={sharedSectionsMap}\n scope={scope}\n pageScope={pageScope}\n fromTemplate={templateSectionKeys.has(section._key)}\n />\n ))}\n </>\n );\n },\n };\n\n const themeSettingsCss = themeSettingsToCss(pageData.theme?.settings);\n\n try {\n const renderedContent = parse(preparedTemplate, parseOptions);\n\n return (\n <>\n {themeSettingsCss && <style dangerouslySetInnerHTML={{ __html: themeSettingsCss }} />}\n {templateCss && <style dangerouslySetInnerHTML={{ __html: templateCss }} />}\n {renderedContent}\n {showModeBadge && (\n <div\n style={{\n position: \"fixed\",\n bottom: \"1rem\",\n right: \"1rem\",\n padding: \"0.5rem 1rem\",\n borderRadius: \"0.5rem\",\n background: \"#111\",\n color: \"#fff\",\n fontSize: \"0.875rem\",\n fontWeight: 500,\n boxShadow: \"0 4px 12px rgba(0,0,0,0.2)\",\n zIndex: 50,\n }}\n >\n {mode === \"draft\" ? \"Draft preview\" : \"Published preview\"}\n </div>\n )}\n </>\n );\n } catch (error) {\n console.error(\"Error rendering template:\", error);\n if (renderError) return <>{renderError(error)}</>;\n return errorBox(\"Template Error\", error instanceof Error ? error.message : \"Failed to render template\");\n }\n}\n\nexport { errorBox as renderErrorBox };\n","export interface ThemeColorSet {\n primary?: string;\n primaryForeground?: string;\n secondary?: string;\n secondaryForeground?: string;\n accent?: string;\n accentForeground?: string;\n background?: string;\n foreground?: string;\n muted?: string;\n mutedForeground?: string;\n border?: string;\n input?: string;\n ring?: string;\n destructive?: string;\n destructiveForeground?: string;\n success?: string;\n successForeground?: string;\n warning?: string;\n warningForeground?: string;\n info?: string;\n infoForeground?: string;\n}\n\nexport interface ThemeSettings {\n colors?: { light?: ThemeColorSet; dark?: ThemeColorSet };\n typography?: {\n fontFamily?: {\n heading?: string;\n body?: string;\n ui?: string;\n code?: string;\n sans?: string;\n serif?: string;\n mono?: string;\n };\n fontSize?: {\n h1?: string; h2?: string; h3?: string; h4?: string; h5?: string; h6?: string;\n xs?: string; sm?: string; base?: string; lg?: string; xl?: string;\n \"2xl\"?: string; \"3xl\"?: string; \"4xl\"?: string;\n };\n fontWeight?: { normal?: string; medium?: string; semibold?: string; bold?: string };\n lineHeight?: { tight?: string; normal?: string; relaxed?: string };\n };\n spacing?: { xs?: string; sm?: string; md?: string; lg?: string; xl?: string; \"2xl\"?: string };\n borderRadius?: { none?: string; sm?: string; md?: string; lg?: string; xl?: string; full?: string };\n shadows?: { sm?: string; md?: string; lg?: string; xl?: string };\n layout?: {\n containerMaxWidth?: string;\n sidebarWidth?: string;\n headerHeight?: string;\n footerHeight?: string;\n };\n}\n\nconst COLOR_KEYS: (keyof ThemeColorSet)[] = [\n \"primary\", \"primaryForeground\", \"secondary\", \"secondaryForeground\", \"accent\", \"accentForeground\",\n \"background\", \"foreground\", \"muted\", \"mutedForeground\", \"border\", \"input\", \"ring\",\n \"destructive\", \"destructiveForeground\", \"success\", \"successForeground\",\n \"warning\", \"warningForeground\", \"info\", \"infoForeground\",\n];\n\n/** Groups of common variables, in output order: [settings path, css prefix, keys]. */\nconst COMMON_GROUPS: [string[], string, string[]][] = [\n [[\"typography\", \"fontFamily\"], \"--font\", [\"heading\", \"body\", \"ui\", \"code\", \"sans\", \"serif\", \"mono\"]],\n [[\"typography\", \"fontSize\"], \"--font-size\", [\"h1\", \"h2\", \"h3\", \"h4\", \"h5\", \"h6\", \"xs\", \"sm\", \"base\", \"lg\", \"xl\", \"2xl\", \"3xl\", \"4xl\"]],\n [[\"typography\", \"fontWeight\"], \"--font-weight\", [\"normal\", \"medium\", \"semibold\", \"bold\"]],\n [[\"typography\", \"lineHeight\"], \"--line-height\", [\"tight\", \"normal\", \"relaxed\"]],\n [[\"spacing\"], \"--spacing\", [\"xs\", \"sm\", \"md\", \"lg\", \"xl\", \"2xl\"]],\n [[\"borderRadius\"], \"--radius\", [\"none\", \"sm\", \"md\", \"lg\", \"xl\", \"full\"]],\n [[\"shadows\"], \"--shadow\", [\"sm\", \"md\", \"lg\", \"xl\"]],\n [[\"layout\"], \"-\", [\"containerMaxWidth\", \"sidebarWidth\", \"headerHeight\", \"footerHeight\"]],\n];\n\nconst kebab = (key: string): string => key.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);\n\nfunction colorVars(colors: ThemeColorSet | undefined): string[] {\n if (!colors) return [];\n const vars: string[] = [];\n for (const key of COLOR_KEYS) {\n const value = colors[key];\n if (value) vars.push(`--color-${kebab(key)}: ${value};`);\n }\n return vars;\n}\n\nfunction pick(settings: Record<string, any>, path: string[]): Record<string, string> | undefined {\n let current: any = settings;\n for (const segment of path) {\n current = current?.[segment];\n if (!current) return undefined;\n }\n return current;\n}\n\n/**\n * Theme settings as CSS custom properties: common variables and light colors\n * on `:root`, dark colors under `prefers-color-scheme: dark` and `.dark`.\n */\nexport function themeSettingsToCss(settings?: ThemeSettings | Record<string, any>): string {\n if (!settings) return \"\";\n\n const s = settings as ThemeSettings;\n const lightVars = colorVars(s.colors?.light);\n const darkVars = colorVars(s.colors?.dark);\n const commonVars: string[] = [];\n\n for (const [path, prefix, keys] of COMMON_GROUPS) {\n const group = pick(settings, path);\n if (!group) continue;\n for (const key of keys) {\n const value = group[key];\n if (value) commonVars.push(`${prefix}-${kebab(key)}: ${value};`);\n }\n }\n\n const cssBlocks: string[] = [];\n\n if (commonVars.length > 0) {\n cssBlocks.push(`:root {\\n ${commonVars.join(\"\\n \")}\\n}`);\n }\n\n if (lightVars.length > 0) {\n cssBlocks.push(`:root {\\n ${lightVars.join(\"\\n \")}\\n}`);\n }\n\n if (darkVars.length > 0) {\n cssBlocks.push(`@media (prefers-color-scheme: dark) {\\n :root {\\n ${darkVars.join(\"\\n \")}\\n }\\n}`);\n cssBlocks.push(`.dark {\\n ${darkVars.join(\"\\n \")}\\n}`);\n }\n\n return cssBlocks.join(\"\\n\\n\");\n}\n","import { filterVisibleNodesRecursive, type VisibilityContext } from \"@botlr/react\";\nimport type { BotlrClient, LayoutNode } from \"@botlr/sdk\";\nimport { environmentFromEnv } from \"./env\";\n\nexport interface StaticTextFileOptions {\n /** Defaults to the client's locale, then `en`. */\n locale?: string;\n /** Environment for visibility rules; defaults to `environmentFromEnv()`. */\n environment?: string;\n /** The section type whose content is served; defaults to `text`. */\n sectionType?: string;\n /** Section props tried in order for the text; defaults to `content`, `body`, `text`. */\n propKeys?: string[];\n}\n\nfunction collectSections(nodes: LayoutNode[], sectionType: string): LayoutNode[] {\n const result: LayoutNode[] = [];\n for (const node of nodes) {\n if (\"mode\" in node && node.mode === \"inline\" && node.sectionType === sectionType) {\n result.push(node);\n } else if (\"type\" in node && node.type === \"grid-container\" && Array.isArray(node.columns)) {\n for (const column of node.columns) {\n result.push(...collectSections(column.sections ?? [], sectionType));\n }\n }\n }\n return result;\n}\n\nconst TEXT_PLAIN = { \"Content-Type\": \"text/plain; charset=utf-8\" };\n\n/**\n * A CMS-managed plain-text file such as `robots.txt`, `humans.txt` or\n * `llms.txt`: the page at `slug`, its visible text sections concatenated.\n * Serves `defaultContent` when the page has none or cannot be fetched.\n *\n * ```ts\n * // app/robots.txt/route.ts\n * export const GET = () => serveStaticTextFile(botlr, \"robots.txt\", defaultRobotsContent());\n * ```\n */\nexport async function serveStaticTextFile(\n client: BotlrClient,\n slug: string,\n defaultContent: string,\n options: StaticTextFileOptions = {},\n): Promise<Response> {\n const locale = options.locale ?? client.config.locale ?? \"en\";\n const environment = options.environment ?? environmentFromEnv();\n const sectionType = options.sectionType ?? \"text\";\n const propKeys = options.propKeys ?? [\"content\", \"body\", \"text\"];\n\n try {\n const page = await client.getPageBySlug(slug, locale);\n if (page?.layout) {\n const visibilityContext: VisibilityContext = { locale, environment };\n const sections: LayoutNode[] = [];\n for (const region of Object.values(page.layout)) {\n if (Array.isArray(region)) {\n sections.push(...collectSections(filterVisibleNodesRecursive(region, visibilityContext), sectionType));\n }\n }\n\n const content = sections\n .map((section) => {\n const props: Record<string, unknown> = \"props\" in section ? (section.props ?? {}) : {};\n const value = propKeys.map((key) => props[key]).find((v) => typeof v === \"string\" && v);\n return typeof value === \"string\" ? value : \"\";\n })\n .filter(Boolean)\n .join(\"\\n\\n\");\n\n if (content) return new Response(content, { headers: TEXT_PLAIN });\n }\n } catch {\n // Fall through to the default\n }\n\n return new Response(defaultContent, { headers: TEXT_PLAIN });\n}\n\n/** Allow everything in production, disallow everything elsewhere. */\nexport function defaultRobotsContent(environment = environmentFromEnv()): string {\n return environment === \"production\" ? \"User-agent: *\\nAllow: /\\n\" : \"User-agent: *\\nDisallow: /\\n\";\n}\n","import type { Metadata } from \"next\";\nimport { notFound, permanentRedirect, redirect } from \"next/navigation\";\nimport type { ReactNode } from \"react\";\nimport type { ComponentRegistry } from \"@botlr/react\";\nimport type { BotlrClient, PublisherScope } from \"@botlr/sdk\";\nimport { environmentFromEnv, scopeFromEnv } from \"./env\";\nimport { buildPageMetadata } from \"./page-metadata\";\nimport { renderCmsPage, renderErrorBox } from \"./render-cms-page\";\nimport { createPageResolver, type ResolvePageOptions, type ResolvedPage } from \"./resolve-page\";\n\nexport interface LocalePageConfig {\n /** CMS locale code, for example `da` or `en`. */\n locale: string;\n /** URL prefix for this locale, for example `/en`. Empty for the root locale. */\n folder?: string;\n /** The client the pages are fetched with; usually one built with `createBotlrFromEnv`. */\n client: BotlrClient;\n componentRegistry: ComponentRegistry;\n /** Overrides on the scope built from the environment. */\n scope?: Partial<PublisherScope>;\n /** The site origin, for canonical and hreflang metadata. */\n siteUrl?: string;\n /** Environment for visibility rules; defaults to `environmentFromEnv()`. */\n environment?: string;\n resolveOptions?: ResolvePageOptions;\n /** Redirect kind for CMS redirects; defaults to `permanent` (308). */\n redirect?: \"permanent\" | \"temporary\";\n /** Page slugs fetched for `generateStaticParams`; defaults to 1000. */\n staticParamsLimit?: number;\n /**\n * What happens when resolving a page throws (after the client's retries).\n * `throw` rethrows, which fails `next build` and gives a 500 at request\n * time; `render` shows an error box in place of the page. Defaults to\n * `render` when `NODE_ENV` is `development` and `throw` otherwise.\n */\n resolveErrors?: \"throw\" | \"render\";\n /** Rendered in place of the page when `resolveErrors` is `render`. */\n renderError?: (message: string) => ReactNode;\n}\n\nexport interface LocalePageProps {\n params: Promise<{ slug?: string[] }>;\n searchParams?: Promise<Record<string, string | string[] | undefined>>;\n}\n\n/** The page slug for a catch-all route: the segments joined, or `index`. */\nexport async function pageSlugFromParams(params: LocalePageProps[\"params\"]): Promise<string> {\n const slug = (await params).slug ?? [];\n return slug.length > 0 ? slug.join(\"/\") : \"index\";\n}\n\n/**\n * Everything a static per-locale `[[...slug]]` route needs. The route file\n * keeps only its segment config, which Next must read statically:\n *\n * ```ts\n * // app/(root)/[[...slug]]/page.tsx\n * export const revalidate = false;\n * export const dynamicParams = false;\n * const page = createLocalePage({ locale: \"da\", folder: \"\", client: botlr, componentRegistry });\n * export const generateStaticParams = page.generateStaticParams;\n * export const generateMetadata = page.generateMetadata;\n * export default page.Page;\n * ```\n */\nexport function createLocalePage(config: LocalePageConfig) {\n const { locale, client, componentRegistry } = config;\n const folder = config.folder ? `/${config.folder.replace(/^\\/+|\\/+$/g, \"\")}` : \"\";\n const scope: PublisherScope = scopeFromEnv({ locale, localePrefix: folder.replace(/^\\//, \"\"), ...config.scope });\n const resolvePage = createPageResolver(client, config.resolveOptions);\n const sendRedirect: (url: string) => never = config.redirect === \"temporary\" ? redirect : permanentRedirect;\n\n async function generateStaticParams(): Promise<{ slug?: string[] }[]> {\n const response = await client.getPageSlugList({ locale, limit: config.staticParamsLimit ?? 1000 });\n return (response.data ?? []).map((path) => ({ slug: path ? path.split(\"/\") : [] }));\n }\n\n async function generateMetadata({ params }: LocalePageProps): Promise<Metadata> {\n const resolved = await resolvePage(await pageSlugFromParams(params), locale);\n if (resolved.kind !== \"page\") return {};\n return buildPageMetadata(resolved.pageData, { siteUrl: config.siteUrl, folder });\n }\n\n async function Page({ params }: LocalePageProps) {\n const slug = await pageSlugFromParams(params);\n const resolved: ResolvedPage = await resolvePage(slug, locale);\n\n if (resolved.kind === \"error\") {\n const mode = config.resolveErrors ?? (process.env.NODE_ENV === \"development\" ? \"render\" : \"throw\");\n if (mode === \"throw\") {\n throw new Error(`@botlr/next: could not resolve \"${slug}\" (${locale}): ${resolved.message}`, {\n cause: resolved.cause,\n });\n }\n if (config.renderError) return <>{config.renderError(resolved.message)}</>;\n return renderErrorBox(\"Error Loading Page\", resolved.message);\n }\n if (resolved.kind === \"404\") notFound();\n if (resolved.kind === \"redirect\") sendRedirect(`${folder}${resolved.destination}`);\n\n const { pageData } = resolved;\n if (pageData.status !== \"published\") notFound();\n\n return renderCmsPage({\n pageData,\n scope,\n componentRegistry,\n mode: \"published\",\n locale,\n environment: config.environment ?? environmentFromEnv(),\n client,\n });\n }\n\n return { generateStaticParams, generateMetadata, Page, resolvePage, scope };\n}\n\n/**\n * A locale folder that only aliases another, for example `/se/*` to `/sv/*`.\n * Permanently redirects, keeping the rest of the path.\n */\nexport function createLocaleAliasPage(targetFolder: string) {\n return async function AliasPage({ params }: LocalePageProps): Promise<never> {\n const slug = (await params).slug ?? [];\n permanentRedirect(`${targetFolder}/${slug.join(\"/\")}`);\n };\n}\n","type Entry = { count: number; resetAt: number };\n\nconst store = new Map<string, Entry>();\n\nexport interface RateLimitConfig {\n /** Max requests allowed in the window */\n limit: number;\n /** Window size in seconds */\n window: number;\n}\n\nexport interface RateLimitResult {\n allowed: boolean;\n remaining: number;\n /** Epoch ms when the window resets */\n resetAt: number;\n}\n\n/**\n * A fixed-window in-memory rate limit, per server instance. Enough to slow\n * down form and forum spam; not shared across instances or restarts.\n *\n * ```ts\n * const rl = rateLimit(`form:${getClientIp(request)}`, { limit: 5, window: 60 });\n * if (!rl.allowed) return new Response(\"Too many requests\", { status: 429 });\n * ```\n */\nexport function rateLimit(key: string, config: RateLimitConfig): RateLimitResult {\n const now = Date.now();\n const windowMs = config.window * 1000;\n\n let entry = store.get(key);\n\n if (!entry || now >= entry.resetAt) {\n entry = { count: 1, resetAt: now + windowMs };\n store.set(key, entry);\n return { allowed: true, remaining: config.limit - 1, resetAt: entry.resetAt };\n }\n\n entry.count++;\n const allowed = entry.count <= config.limit;\n\n return {\n allowed,\n remaining: Math.max(0, config.limit - entry.count),\n resetAt: entry.resetAt,\n };\n}\n\n/** The client IP from `x-forwarded-for` or `x-real-ip`, or `\"unknown\"`. */\nexport function getClientIp(request: Request): string {\n return (\n request.headers.get(\"x-forwarded-for\")?.split(\",\")[0]?.trim() ||\n request.headers.get(\"x-real-ip\") ||\n \"unknown\"\n );\n}\n"],"mappings":"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","names":["import_react","import_react","parse","import_react","import_jsx_runtime","resolvePage","import_sdk"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BotlrClientConfig, BotlrClient, PublisherScope, PageResponse, LayoutNode } from '@botlr/sdk';
|
|
2
2
|
export * from '@botlr/sdk';
|
|
3
3
|
export { BotlrConflictError, BotlrError, createAssetUrl, createBotlrClient, getAssetFilename, getAssetId, getAssetUrl, isAssetData } from '@botlr/sdk';
|
|
4
4
|
import { Metadata } from 'next';
|
|
@@ -20,6 +20,8 @@ type GetBotlrParams = {
|
|
|
20
20
|
/** API origin; falls back to `BOTLR_API_URL`. */
|
|
21
21
|
baseUrl?: string;
|
|
22
22
|
fetch?: typeof fetch;
|
|
23
|
+
/** Retry policy for failed reads; see `BotlrClientConfig.retry`. */
|
|
24
|
+
retry?: BotlrClientConfig["retry"];
|
|
23
25
|
};
|
|
24
26
|
/**
|
|
25
27
|
* A Botlr client for the current request, for Server Components, route
|
|
@@ -88,6 +90,7 @@ type ResolvedPage = {
|
|
|
88
90
|
} | {
|
|
89
91
|
kind: "error";
|
|
90
92
|
message: string;
|
|
93
|
+
cause: unknown;
|
|
91
94
|
};
|
|
92
95
|
interface ResolvePageOptions {
|
|
93
96
|
/**
|
|
@@ -315,7 +318,14 @@ interface LocalePageConfig {
|
|
|
315
318
|
redirect?: "permanent" | "temporary";
|
|
316
319
|
/** Page slugs fetched for `generateStaticParams`; defaults to 1000. */
|
|
317
320
|
staticParamsLimit?: number;
|
|
318
|
-
/**
|
|
321
|
+
/**
|
|
322
|
+
* What happens when resolving a page throws (after the client's retries).
|
|
323
|
+
* `throw` rethrows, which fails `next build` and gives a 500 at request
|
|
324
|
+
* time; `render` shows an error box in place of the page. Defaults to
|
|
325
|
+
* `render` when `NODE_ENV` is `development` and `throw` otherwise.
|
|
326
|
+
*/
|
|
327
|
+
resolveErrors?: "throw" | "render";
|
|
328
|
+
/** Rendered in place of the page when `resolveErrors` is `render`. */
|
|
319
329
|
renderError?: (message: string) => ReactNode;
|
|
320
330
|
}
|
|
321
331
|
interface LocalePageProps {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BotlrClientConfig, BotlrClient, PublisherScope, PageResponse, LayoutNode } from '@botlr/sdk';
|
|
2
2
|
export * from '@botlr/sdk';
|
|
3
3
|
export { BotlrConflictError, BotlrError, createAssetUrl, createBotlrClient, getAssetFilename, getAssetId, getAssetUrl, isAssetData } from '@botlr/sdk';
|
|
4
4
|
import { Metadata } from 'next';
|
|
@@ -20,6 +20,8 @@ type GetBotlrParams = {
|
|
|
20
20
|
/** API origin; falls back to `BOTLR_API_URL`. */
|
|
21
21
|
baseUrl?: string;
|
|
22
22
|
fetch?: typeof fetch;
|
|
23
|
+
/** Retry policy for failed reads; see `BotlrClientConfig.retry`. */
|
|
24
|
+
retry?: BotlrClientConfig["retry"];
|
|
23
25
|
};
|
|
24
26
|
/**
|
|
25
27
|
* A Botlr client for the current request, for Server Components, route
|
|
@@ -88,6 +90,7 @@ type ResolvedPage = {
|
|
|
88
90
|
} | {
|
|
89
91
|
kind: "error";
|
|
90
92
|
message: string;
|
|
93
|
+
cause: unknown;
|
|
91
94
|
};
|
|
92
95
|
interface ResolvePageOptions {
|
|
93
96
|
/**
|
|
@@ -315,7 +318,14 @@ interface LocalePageConfig {
|
|
|
315
318
|
redirect?: "permanent" | "temporary";
|
|
316
319
|
/** Page slugs fetched for `generateStaticParams`; defaults to 1000. */
|
|
317
320
|
staticParamsLimit?: number;
|
|
318
|
-
/**
|
|
321
|
+
/**
|
|
322
|
+
* What happens when resolving a page throws (after the client's retries).
|
|
323
|
+
* `throw` rethrows, which fails `next build` and gives a 500 at request
|
|
324
|
+
* time; `render` shows an error box in place of the page. Defaults to
|
|
325
|
+
* `render` when `NODE_ENV` is `development` and `throw` otherwise.
|
|
326
|
+
*/
|
|
327
|
+
resolveErrors?: "throw" | "render";
|
|
328
|
+
/** Rendered in place of the page when `resolveErrors` is `render`. */
|
|
319
329
|
renderError?: (message: string) => ReactNode;
|
|
320
330
|
}
|
|
321
331
|
interface LocalePageProps {
|
package/dist/index.js
CHANGED
|
@@ -64,7 +64,8 @@ async function getBotlrForRoute(params = {}) {
|
|
|
64
64
|
previewToken: params.previewToken,
|
|
65
65
|
locale,
|
|
66
66
|
preview: params.preview,
|
|
67
|
-
fetch: params.fetch
|
|
67
|
+
fetch: params.fetch,
|
|
68
|
+
retry: params.retry
|
|
68
69
|
})
|
|
69
70
|
);
|
|
70
71
|
}
|
|
@@ -122,7 +123,8 @@ async function resolvePage(client, slug, locale, options = {}) {
|
|
|
122
123
|
} catch (error) {
|
|
123
124
|
return {
|
|
124
125
|
kind: "error",
|
|
125
|
-
message: error instanceof Error ? error.message : "Unknown error"
|
|
126
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
127
|
+
cause: error
|
|
126
128
|
};
|
|
127
129
|
}
|
|
128
130
|
}
|
|
@@ -523,8 +525,15 @@ function createLocalePage(config) {
|
|
|
523
525
|
return buildPageMetadata(resolved.pageData, { siteUrl: config.siteUrl, folder });
|
|
524
526
|
}
|
|
525
527
|
async function Page({ params }) {
|
|
526
|
-
const
|
|
528
|
+
const slug = await pageSlugFromParams(params);
|
|
529
|
+
const resolved = await resolvePage2(slug, locale);
|
|
527
530
|
if (resolved.kind === "error") {
|
|
531
|
+
const mode = config.resolveErrors ?? (process.env.NODE_ENV === "development" ? "render" : "throw");
|
|
532
|
+
if (mode === "throw") {
|
|
533
|
+
throw new Error(`@botlr/next: could not resolve "${slug}" (${locale}): ${resolved.message}`, {
|
|
534
|
+
cause: resolved.cause
|
|
535
|
+
});
|
|
536
|
+
}
|
|
528
537
|
if (config.renderError) return /* @__PURE__ */ jsx2(Fragment2, { children: config.renderError(resolved.message) });
|
|
529
538
|
return errorBox("Error Loading Page", resolved.message);
|
|
530
539
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.ts","../src/env.ts","../src/locale.ts","../src/resolve-page.ts","../src/page-metadata.ts","../src/render-cms-page.tsx","../src/theme-css.ts","../src/static-text-file.ts","../src/locale-page.tsx","../src/rate-limit.ts","../src/index.ts"],"sourcesContent":["import { headers } from \"next/headers\";\nimport { cache } from \"react\";\nimport { createBotlrClient, type BotlrClient } from \"@botlr/sdk\";\nimport { configFromEnv } from \"./env\";\nimport { localeFromAcceptLanguage } from \"./locale\";\n\nexport type GetBotlrParams = {\n /** The app instance ID. Falls back to `BOTLR_APP_ID`. */\n appId?: string;\n /** Locale override; defaults to the request's `Accept-Language`, then `en`. */\n locale?: string;\n preview?: boolean;\n /** Site token; falls back to `BOTLR_API_KEY`. Omit in dev mode. */\n apiKey?: string;\n /** Preview JWT, forwarded to `/api/resolve` in preview mode. */\n previewToken?: string;\n /** API origin; falls back to `BOTLR_API_URL`. */\n baseUrl?: string;\n fetch?: typeof fetch;\n};\n\n/**\n * A Botlr client for the current request, for Server Components, route\n * handlers and server actions. Reads the locale from `Accept-Language`\n * unless given.\n *\n * ```ts\n * const botlr = await getBotlrForRoute({ appId: scope.appInstanceId, locale: scope.locale, preview });\n * const pages = await botlr.getPageList({ tags: [\"news\"] });\n * ```\n */\nexport async function getBotlrForRoute(params: GetBotlrParams = {}): Promise<BotlrClient> {\n const h = await headers();\n const locale = params.locale || localeFromAcceptLanguage(h.get(\"accept-language\"));\n return createBotlrClient(\n configFromEnv({\n appId: params.appId,\n apiKey: params.apiKey,\n baseUrl: params.baseUrl,\n previewToken: params.previewToken,\n locale,\n preview: params.preview,\n fetch: params.fetch,\n }),\n );\n}\n\n/**\n * `getBotlrForRoute` memoised per request with React `cache`, so several\n * components in one render share a client.\n */\nexport const getCachedBotlr: (params?: GetBotlrParams) => Promise<BotlrClient> = cache(getBotlrForRoute);\n\n/**\n * A client from the environment alone, with no request context. For scripts,\n * `generateStaticParams`, instrumentation and anywhere `headers()` is not\n * available.\n */\nexport function createBotlrFromEnv(overrides: Partial<GetBotlrParams> = {}): BotlrClient {\n return createBotlrClient(configFromEnv(overrides));\n}\n","import type { BotlrClientConfig, PublisherScope } from \"@botlr/sdk\";\n\n/** Environment variables the helpers read. All server-side: the API URL is never exposed to the browser. */\nexport const BOTLR_ENV = {\n apiUrl: \"BOTLR_API_URL\",\n apiKey: \"BOTLR_API_KEY\",\n appId: \"BOTLR_APP_ID\",\n pat: \"BOTLR_PAT\",\n orgSlug: \"BOTLR_ORG_SLUG\",\n projectSlug: \"BOTLR_PROJECT_SLUG\",\n appInstanceSlug: \"BOTLR_APP_INSTANCE_SLUG\",\n environment: \"BOTLR_ENVIRONMENT\",\n} as const;\n\nexport const DEFAULT_API_URL = \"http://localhost:8888\";\n\nfunction env(name: string): string | undefined {\n const value = typeof process !== \"undefined\" ? process.env?.[name] : undefined;\n return value === \"\" ? undefined : value;\n}\n\n/**\n * Client config from the environment, with `overrides` winning. Throws when\n * no app id is known, since every Botlr request needs one.\n */\nexport function configFromEnv(overrides: Partial<BotlrClientConfig> = {}): BotlrClientConfig {\n const appId = overrides.appId ?? env(BOTLR_ENV.appId);\n if (!appId) {\n throw new Error(`@botlr/next: pass appId or set ${BOTLR_ENV.appId}`);\n }\n return {\n ...overrides,\n baseUrl: overrides.baseUrl ?? env(BOTLR_ENV.apiUrl) ?? DEFAULT_API_URL,\n appId,\n apiKey: overrides.apiKey ?? env(BOTLR_ENV.apiKey),\n personalToken: overrides.personalToken ?? env(BOTLR_ENV.pat),\n };\n}\n\n/**\n * The deployment environment used by visibility rules: `BOTLR_ENVIRONMENT`,\n * then `ENVIRONMENT`, then `NODE_ENV`, then `fallback`.\n */\nexport function environmentFromEnv(fallback = \"production\"): string {\n return env(BOTLR_ENV.environment) ?? env(\"ENVIRONMENT\") ?? env(\"NODE_ENV\") ?? fallback;\n}\n\n/**\n * The `PublisherScope` for a locale from `BOTLR_ORG_SLUG`, `BOTLR_PROJECT_SLUG`,\n * `BOTLR_APP_INSTANCE_SLUG` and `BOTLR_APP_ID`, with `overrides` winning.\n * Missing slugs become empty strings rather than errors, since only asset\n * URLs need them.\n */\nexport function scopeFromEnv(overrides: Partial<PublisherScope> & Pick<PublisherScope, \"locale\">): PublisherScope {\n return {\n orgSlug: env(BOTLR_ENV.orgSlug) ?? \"\",\n projectSlug: env(BOTLR_ENV.projectSlug) ?? \"\",\n appInstanceSlug: env(BOTLR_ENV.appInstanceSlug) ?? \"\",\n appInstanceId: env(BOTLR_ENV.appId) ?? \"\",\n ...overrides,\n };\n}\n","/** The first language of an `Accept-Language` value, or `fallback`. */\nexport function localeFromAcceptLanguage(header: string | null | undefined, fallback = \"en\"): string {\n if (!header) return fallback;\n const first = header.split(\",\")[0]?.split(\";\")[0]?.trim();\n return first || fallback;\n}\n","import { cache } from \"react\";\nimport type { BotlrClient, InlineSectionInstance, PageResponse } from \"@botlr/sdk\";\n\nexport type ResolvedPage =\n | { kind: \"page\"; pageData: PageResponse }\n | { kind: \"redirect\"; destination: string }\n | { kind: \"404\" }\n | { kind: \"error\"; message: string };\n\nexport interface ResolvePageOptions {\n /**\n * Non-page content types rendered through a section: the resolved content\n * is prepended to the `main` region as an inline section of the mapped\n * type. Defaults to `{ article: \"article\" }`.\n */\n contentSections?: Record<string, string>;\n}\n\nconst DEFAULT_CONTENT_SECTIONS: Record<string, string> = { article: \"article\" };\n\n/**\n * Resolves a slug to a page through `/api/resolve`, folding redirects, 404s\n * and errors into one result and injecting article content into the layout.\n * Not cached; see `createPageResolver`.\n */\nexport async function resolvePage(\n client: BotlrClient,\n slug: string,\n locale?: string,\n options: ResolvePageOptions = {},\n): Promise<ResolvedPage> {\n try {\n const result = await client.resolveUrl(slug, locale);\n\n if (result.kind === \"404\") return { kind: \"404\" };\n if (result.kind === \"redirect\" && result.redirect) {\n return { kind: \"redirect\", destination: result.redirect.destination };\n }\n if (!result.data) return { kind: \"404\" };\n\n const data = result.data;\n const layout = data.layout ?? {};\n\n const content = data.content ?? {};\n const contentType: string | null = content[\"type\"] ?? data.type ?? null;\n if (contentType && contentType !== \"page\") {\n const sectionType = (options.contentSections ?? DEFAULT_CONTENT_SECTIONS)[contentType];\n if (sectionType) {\n const section: InlineSectionInstance = {\n _key: `${sectionType}-${content[\"id\"] ?? data.id}`,\n mode: \"inline\",\n sectionType,\n props: content,\n status: \"published\",\n };\n layout[\"main\"] = [section, ...(layout[\"main\"] ?? [])];\n } else {\n console.warn(\"Unknown content type:\", contentType);\n }\n }\n\n return {\n kind: \"page\",\n pageData: {\n id: data.id,\n name: data.name,\n slug: data.slug || \"\",\n full_slug: data.full_slug || \"\",\n layout,\n status: data.status,\n theme: data.theme,\n page_template: data.page_template,\n seo_meta: data.seo_meta,\n alternate_translations: result.alternate_translations ?? [],\n breadcrumbs: result.breadcrumbs ?? [],\n },\n };\n } catch (error) {\n return {\n kind: \"error\",\n message: error instanceof Error ? error.message : \"Unknown error\",\n };\n }\n}\n\n/**\n * `resolvePage` bound to a client and memoised per request with React\n * `cache`, so `generateMetadata` and the page share one fetch.\n *\n * ```ts\n * const resolvePage = createPageResolver(botlr);\n * const resolved = await resolvePage(\"about/team\", \"en\");\n * ```\n */\nexport function createPageResolver(\n client: BotlrClient,\n options: ResolvePageOptions = {},\n): (slug: string, locale?: string) => Promise<ResolvedPage> {\n return cache((slug: string, locale?: string) => resolvePage(client, slug, locale, options));\n}\n","import type { Metadata } from \"next\";\nimport type { PageResponse } from \"@botlr/sdk\";\n\nexport interface PageMetadataOptions {\n /**\n * The site origin, for example `https://www.example.com`. Needed for the\n * canonical URL and hreflang alternates; without it only an explicit\n * `seo_meta.canonical` is emitted.\n */\n siteUrl?: string;\n /** URL prefix of the page's locale, for example `/en`. Empty for the root locale. */\n folder?: string;\n}\n\n/**\n * Next.js `Metadata` from a page's `seo_meta`: title, description, keywords,\n * Open Graph, Twitter, robots, canonical and hreflang alternates.\n */\nexport function buildPageMetadata(pageData: PageResponse, options: PageMetadataOptions = {}): Metadata {\n const seo = pageData.seo_meta || {};\n const title = seo.title || pageData.name;\n const alternates = pageData.alternate_translations || [];\n const siteUrl = options.siteUrl?.replace(/\\/+$/, \"\");\n const folder = options.folder ? `/${options.folder.replace(/^\\/+|\\/+$/g, \"\")}` : \"\";\n\n const metadata: Metadata = { title };\n\n if (seo.description) metadata.description = seo.description;\n if (seo.keywords) metadata.keywords = seo.keywords;\n\n const ogImage = seo.ogImage?.url || seo.ogImage?.filename;\n const description = seo.ogDescription || seo.description;\n metadata.openGraph = {\n title: seo.ogTitle || title,\n ...(description ? { description } : {}),\n ...(seo.ogType ? { type: seo.ogType } : {}),\n ...(ogImage ? { images: [ogImage] } : {}),\n };\n\n if (seo.twitterCard) {\n metadata.twitter = {\n card: seo.twitterCard,\n title: seo.ogTitle || title,\n ...(description ? { description } : {}),\n ...(ogImage ? { images: [ogImage] } : {}),\n };\n }\n\n if (seo.noindex !== undefined || seo.nofollow !== undefined || seo.robots) {\n metadata.robots = seo.robots ?? { index: !seo.noindex, follow: !seo.nofollow };\n }\n\n if (seo.canonical) {\n metadata.alternates = { canonical: seo.canonical };\n } else if (siteUrl) {\n const canonical = pageData.full_slug\n ? `${siteUrl}${folder}/${pageData.full_slug}`\n : `${siteUrl}${folder}`;\n metadata.alternates = { canonical };\n }\n\n if (siteUrl && alternates.length > 0) {\n const languages: Record<string, string> = {};\n for (const alt of alternates) {\n languages[alt.locale] = `${siteUrl}${alt.full_slug}`;\n }\n metadata.alternates = { ...metadata.alternates, languages };\n }\n\n return metadata;\n}\n","import type { JSX, ReactNode } from \"react\";\nimport parse, { type HTMLReactParserOptions } from \"html-react-parser\";\nimport {\n SectionRenderer,\n filterVisibleNodesRecursive,\n type ComponentRegistry,\n type VisibilityContext,\n} from \"@botlr/react\";\nimport type { BotlrClient, LayoutNode, PageResponse, PageScope, PublisherScope } from \"@botlr/sdk\";\nimport { environmentFromEnv } from \"./env\";\nimport { themeSettingsToCss } from \"./theme-css\";\n\ntype Layout = Record<string, LayoutNode[]>;\n\nexport interface RenderCmsPageOptions {\n pageData: PageResponse;\n scope: PublisherScope;\n /** Section type to component map used for every region. */\n componentRegistry: ComponentRegistry;\n /** Defaults to `\"published\"`; `\"draft\"` also renders draft sections. */\n mode?: \"draft\" | \"published\";\n /** Defaults to `scope.locale`. */\n locale?: string;\n /** Environment for visibility rules; defaults to `environmentFromEnv()`. */\n environment?: string;\n /** Used to fetch shared sections unless `fetchSharedSection` is given. */\n client?: BotlrClient;\n /** Fetch a shared section's page by id. Defaults to `client.getPageBySlug(id, locale)`. */\n fetchSharedSection?: (id: string) => Promise<{ layout?: Partial<Layout> } | null>;\n /** Show a fixed \"draft/published preview\" badge; for preview routes only. */\n showModeBadge?: boolean;\n /** Rendered when the template cannot be parsed. */\n renderError?: (error: unknown) => ReactNode;\n}\n\nconst DEFAULT_TEMPLATE = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>__PAGE_TITLE__</title>\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }\n </style>\n __THEME_CSS__\n</head>\n<body>\n <main>\n __REGION_MAIN__\n </main>\n</body>\n</html>`;\n\nfunction extractBodyContent(html: string): string {\n const bodyMatch = html.match(/<body[^>]*>([\\s\\S]*)<\\/body>/i);\n return bodyMatch?.[1] ? bodyMatch[1].trim() : html;\n}\n\n/** Ids of every shared section in the layout, grid columns included. */\nexport function collectSharedSectionIds(nodes: LayoutNode[]): string[] {\n const ids: string[] = [];\n for (const node of nodes) {\n if (\"mode\" in node && node.mode === \"shared\" && node.sharedSectionId) {\n ids.push(node.sharedSectionId);\n }\n if (\"type\" in node && node.type === \"grid-container\" && Array.isArray(node.columns)) {\n for (const column of node.columns) {\n if (Array.isArray(column.sections)) ids.push(...collectSharedSectionIds(column.sections));\n }\n }\n }\n return ids;\n}\n\n/**\n * The template's `{{...}}` placeholders resolved: title, lang, theme css,\n * `{{{REGION}}}` markers turned into text markers for the parser, and the\n * body extracted.\n */\nexport function prepareTemplate(templateHtml: string, pageData: PageResponse, templateCss: string, locale: string): string {\n let prepared = templateHtml\n .replace(/\\{\\{page_title\\}\\}/g, pageData.name)\n .replace(/\\{\\{page_lang\\}\\}/g, locale)\n .replace(/\\{\\{page_description\\}\\}/g, \"\");\n\n prepared = prepared.replace(/\\{\\{\\{theme_css\\}\\}\\}/g, templateCss ? `<style>${templateCss}</style>` : \"\");\n prepared = prepared.replace(/\\{\\{\\{([A-Z_]+)\\}\\}\\}/g, \"__REGION_$1__\");\n prepared = prepared.replace(/\\{\\{#if\\s+([A-Z_]+)\\}\\}/g, \"\");\n prepared = prepared.replace(/\\{\\{\\/if\\}\\}/g, \"\");\n\n return extractBodyContent(prepared);\n}\n\n/** Template sections first, then the page's own, per region. */\nexport function mergeTemplateLayout(pageData: PageResponse): Layout {\n const merged: Layout = {};\n const regions = new Set([...Object.keys(pageData.layout), ...Object.keys(pageData.page_template?.layout ?? {})]);\n for (const region of regions) {\n merged[region] = [...(pageData.page_template?.layout?.[region] ?? []), ...(pageData.layout[region] ?? [])];\n }\n return merged;\n}\n\nconst errorBox = (heading: string, message: string): JSX.Element => (\n <div style={{ display: \"flex\", alignItems: \"center\", justifyContent: \"center\", minHeight: \"100vh\" }}>\n <div style={{ textAlign: \"center\" }}>\n <h1 style={{ fontSize: \"1.5rem\", fontWeight: 700, marginBottom: \"0.5rem\" }}>{heading}</h1>\n <p style={{ opacity: 0.7 }}>{message}</p>\n </div>\n </div>\n);\n\n/**\n * Renders a resolved page: picks the page template (page, then theme, then a\n * built-in default), merges template and page layouts, applies visibility\n * rules, fetches shared sections and mounts a `SectionRenderer` at every\n * `{{{REGION}}}` marker.\n */\nexport async function renderCmsPage({\n pageData,\n scope,\n componentRegistry,\n mode = \"published\",\n locale = scope.locale,\n environment = environmentFromEnv(),\n client,\n fetchSharedSection,\n showModeBadge = false,\n renderError,\n}: RenderCmsPageOptions): Promise<JSX.Element> {\n let templateHtml: string;\n let templateCss = \"\";\n\n if (pageData.page_template?.template_html) {\n templateHtml = pageData.page_template.template_html;\n templateCss = pageData.page_template.template_css || \"\";\n } else if (pageData.theme?.default_template_html) {\n templateHtml = pageData.theme.default_template_html;\n templateCss = pageData.theme.default_template_css || \"\";\n } else {\n templateHtml = DEFAULT_TEMPLATE;\n }\n\n const preparedTemplate = prepareTemplate(templateHtml, pageData, templateCss, locale);\n\n const pageScope: PageScope = {\n id: pageData.id,\n kind: \"page\",\n name: pageData.name,\n slug: pageData.slug || \"\",\n breadcrumbs: pageData.breadcrumbs || [],\n };\n\n const visibilityContext: VisibilityContext = {\n pageId: pageData.id,\n currentPath: \"/\" + (pageData.slug || \"\"),\n locale,\n environment,\n };\n const filteredLayout: Layout = {};\n for (const [region, sections] of Object.entries(mergeTemplateLayout(pageData))) {\n filteredLayout[region] = filterVisibleNodesRecursive(sections, visibilityContext);\n }\n\n const fetchShared =\n fetchSharedSection ??\n (client ? (id: string) => client.getPageBySlug(id, locale) : undefined);\n const sharedSectionIds = Object.values(filteredLayout).flatMap(collectSharedSectionIds);\n const sharedSectionsMap = new Map<string, LayoutNode[]>();\n\n if (sharedSectionIds.length > 0) {\n if (!fetchShared) {\n throw new Error(\"@botlr/next: renderCmsPage needs `client` or `fetchSharedSection` to render shared sections\");\n }\n const results = await Promise.all(\n [...new Set(sharedSectionIds)].map(async (id) => {\n try {\n const data = await fetchShared(id);\n const main = data?.layout?.[\"main\"];\n if (Array.isArray(main) && main.length > 0) return { id, sections: main };\n } catch (err) {\n console.error(`Error fetching shared section ${id}:`, err);\n }\n return null;\n }),\n );\n for (const result of results) {\n if (result) sharedSectionsMap.set(result.id, result.sections);\n }\n }\n\n const parseOptions: HTMLReactParserOptions = {\n replace: (domNode) => {\n if (!(\"data\" in domNode) || typeof domNode.data !== \"string\") return;\n const regionMatch = domNode.data.match(/__REGION_([A-Z_]+)__/);\n if (!regionMatch?.[1]) return;\n\n const regionKey = regionMatch[1].toLowerCase();\n const regionSections = filteredLayout[regionKey] ?? [];\n if (regionSections.length === 0) return <></>;\n\n const templateSectionKeys = new Set(\n (pageData.page_template?.layout?.[regionKey] ?? []).map((section) => section._key),\n );\n\n return (\n <>\n {regionSections.map((section) => (\n <SectionRenderer\n key={section._key}\n section={section}\n componentRegistry={componentRegistry}\n mode={mode}\n sharedSectionsData={sharedSectionsMap}\n scope={scope}\n pageScope={pageScope}\n fromTemplate={templateSectionKeys.has(section._key)}\n />\n ))}\n </>\n );\n },\n };\n\n const themeSettingsCss = themeSettingsToCss(pageData.theme?.settings);\n\n try {\n const renderedContent = parse(preparedTemplate, parseOptions);\n\n return (\n <>\n {themeSettingsCss && <style dangerouslySetInnerHTML={{ __html: themeSettingsCss }} />}\n {templateCss && <style dangerouslySetInnerHTML={{ __html: templateCss }} />}\n {renderedContent}\n {showModeBadge && (\n <div\n style={{\n position: \"fixed\",\n bottom: \"1rem\",\n right: \"1rem\",\n padding: \"0.5rem 1rem\",\n borderRadius: \"0.5rem\",\n background: \"#111\",\n color: \"#fff\",\n fontSize: \"0.875rem\",\n fontWeight: 500,\n boxShadow: \"0 4px 12px rgba(0,0,0,0.2)\",\n zIndex: 50,\n }}\n >\n {mode === \"draft\" ? \"Draft preview\" : \"Published preview\"}\n </div>\n )}\n </>\n );\n } catch (error) {\n console.error(\"Error rendering template:\", error);\n if (renderError) return <>{renderError(error)}</>;\n return errorBox(\"Template Error\", error instanceof Error ? error.message : \"Failed to render template\");\n }\n}\n\nexport { errorBox as renderErrorBox };\n","export interface ThemeColorSet {\n primary?: string;\n primaryForeground?: string;\n secondary?: string;\n secondaryForeground?: string;\n accent?: string;\n accentForeground?: string;\n background?: string;\n foreground?: string;\n muted?: string;\n mutedForeground?: string;\n border?: string;\n input?: string;\n ring?: string;\n destructive?: string;\n destructiveForeground?: string;\n success?: string;\n successForeground?: string;\n warning?: string;\n warningForeground?: string;\n info?: string;\n infoForeground?: string;\n}\n\nexport interface ThemeSettings {\n colors?: { light?: ThemeColorSet; dark?: ThemeColorSet };\n typography?: {\n fontFamily?: {\n heading?: string;\n body?: string;\n ui?: string;\n code?: string;\n sans?: string;\n serif?: string;\n mono?: string;\n };\n fontSize?: {\n h1?: string; h2?: string; h3?: string; h4?: string; h5?: string; h6?: string;\n xs?: string; sm?: string; base?: string; lg?: string; xl?: string;\n \"2xl\"?: string; \"3xl\"?: string; \"4xl\"?: string;\n };\n fontWeight?: { normal?: string; medium?: string; semibold?: string; bold?: string };\n lineHeight?: { tight?: string; normal?: string; relaxed?: string };\n };\n spacing?: { xs?: string; sm?: string; md?: string; lg?: string; xl?: string; \"2xl\"?: string };\n borderRadius?: { none?: string; sm?: string; md?: string; lg?: string; xl?: string; full?: string };\n shadows?: { sm?: string; md?: string; lg?: string; xl?: string };\n layout?: {\n containerMaxWidth?: string;\n sidebarWidth?: string;\n headerHeight?: string;\n footerHeight?: string;\n };\n}\n\nconst COLOR_KEYS: (keyof ThemeColorSet)[] = [\n \"primary\", \"primaryForeground\", \"secondary\", \"secondaryForeground\", \"accent\", \"accentForeground\",\n \"background\", \"foreground\", \"muted\", \"mutedForeground\", \"border\", \"input\", \"ring\",\n \"destructive\", \"destructiveForeground\", \"success\", \"successForeground\",\n \"warning\", \"warningForeground\", \"info\", \"infoForeground\",\n];\n\n/** Groups of common variables, in output order: [settings path, css prefix, keys]. */\nconst COMMON_GROUPS: [string[], string, string[]][] = [\n [[\"typography\", \"fontFamily\"], \"--font\", [\"heading\", \"body\", \"ui\", \"code\", \"sans\", \"serif\", \"mono\"]],\n [[\"typography\", \"fontSize\"], \"--font-size\", [\"h1\", \"h2\", \"h3\", \"h4\", \"h5\", \"h6\", \"xs\", \"sm\", \"base\", \"lg\", \"xl\", \"2xl\", \"3xl\", \"4xl\"]],\n [[\"typography\", \"fontWeight\"], \"--font-weight\", [\"normal\", \"medium\", \"semibold\", \"bold\"]],\n [[\"typography\", \"lineHeight\"], \"--line-height\", [\"tight\", \"normal\", \"relaxed\"]],\n [[\"spacing\"], \"--spacing\", [\"xs\", \"sm\", \"md\", \"lg\", \"xl\", \"2xl\"]],\n [[\"borderRadius\"], \"--radius\", [\"none\", \"sm\", \"md\", \"lg\", \"xl\", \"full\"]],\n [[\"shadows\"], \"--shadow\", [\"sm\", \"md\", \"lg\", \"xl\"]],\n [[\"layout\"], \"-\", [\"containerMaxWidth\", \"sidebarWidth\", \"headerHeight\", \"footerHeight\"]],\n];\n\nconst kebab = (key: string): string => key.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);\n\nfunction colorVars(colors: ThemeColorSet | undefined): string[] {\n if (!colors) return [];\n const vars: string[] = [];\n for (const key of COLOR_KEYS) {\n const value = colors[key];\n if (value) vars.push(`--color-${kebab(key)}: ${value};`);\n }\n return vars;\n}\n\nfunction pick(settings: Record<string, any>, path: string[]): Record<string, string> | undefined {\n let current: any = settings;\n for (const segment of path) {\n current = current?.[segment];\n if (!current) return undefined;\n }\n return current;\n}\n\n/**\n * Theme settings as CSS custom properties: common variables and light colors\n * on `:root`, dark colors under `prefers-color-scheme: dark` and `.dark`.\n */\nexport function themeSettingsToCss(settings?: ThemeSettings | Record<string, any>): string {\n if (!settings) return \"\";\n\n const s = settings as ThemeSettings;\n const lightVars = colorVars(s.colors?.light);\n const darkVars = colorVars(s.colors?.dark);\n const commonVars: string[] = [];\n\n for (const [path, prefix, keys] of COMMON_GROUPS) {\n const group = pick(settings, path);\n if (!group) continue;\n for (const key of keys) {\n const value = group[key];\n if (value) commonVars.push(`${prefix}-${kebab(key)}: ${value};`);\n }\n }\n\n const cssBlocks: string[] = [];\n\n if (commonVars.length > 0) {\n cssBlocks.push(`:root {\\n ${commonVars.join(\"\\n \")}\\n}`);\n }\n\n if (lightVars.length > 0) {\n cssBlocks.push(`:root {\\n ${lightVars.join(\"\\n \")}\\n}`);\n }\n\n if (darkVars.length > 0) {\n cssBlocks.push(`@media (prefers-color-scheme: dark) {\\n :root {\\n ${darkVars.join(\"\\n \")}\\n }\\n}`);\n cssBlocks.push(`.dark {\\n ${darkVars.join(\"\\n \")}\\n}`);\n }\n\n return cssBlocks.join(\"\\n\\n\");\n}\n","import { filterVisibleNodesRecursive, type VisibilityContext } from \"@botlr/react\";\nimport type { BotlrClient, LayoutNode } from \"@botlr/sdk\";\nimport { environmentFromEnv } from \"./env\";\n\nexport interface StaticTextFileOptions {\n /** Defaults to the client's locale, then `en`. */\n locale?: string;\n /** Environment for visibility rules; defaults to `environmentFromEnv()`. */\n environment?: string;\n /** The section type whose content is served; defaults to `text`. */\n sectionType?: string;\n /** Section props tried in order for the text; defaults to `content`, `body`, `text`. */\n propKeys?: string[];\n}\n\nfunction collectSections(nodes: LayoutNode[], sectionType: string): LayoutNode[] {\n const result: LayoutNode[] = [];\n for (const node of nodes) {\n if (\"mode\" in node && node.mode === \"inline\" && node.sectionType === sectionType) {\n result.push(node);\n } else if (\"type\" in node && node.type === \"grid-container\" && Array.isArray(node.columns)) {\n for (const column of node.columns) {\n result.push(...collectSections(column.sections ?? [], sectionType));\n }\n }\n }\n return result;\n}\n\nconst TEXT_PLAIN = { \"Content-Type\": \"text/plain; charset=utf-8\" };\n\n/**\n * A CMS-managed plain-text file such as `robots.txt`, `humans.txt` or\n * `llms.txt`: the page at `slug`, its visible text sections concatenated.\n * Serves `defaultContent` when the page has none or cannot be fetched.\n *\n * ```ts\n * // app/robots.txt/route.ts\n * export const GET = () => serveStaticTextFile(botlr, \"robots.txt\", defaultRobotsContent());\n * ```\n */\nexport async function serveStaticTextFile(\n client: BotlrClient,\n slug: string,\n defaultContent: string,\n options: StaticTextFileOptions = {},\n): Promise<Response> {\n const locale = options.locale ?? client.config.locale ?? \"en\";\n const environment = options.environment ?? environmentFromEnv();\n const sectionType = options.sectionType ?? \"text\";\n const propKeys = options.propKeys ?? [\"content\", \"body\", \"text\"];\n\n try {\n const page = await client.getPageBySlug(slug, locale);\n if (page?.layout) {\n const visibilityContext: VisibilityContext = { locale, environment };\n const sections: LayoutNode[] = [];\n for (const region of Object.values(page.layout)) {\n if (Array.isArray(region)) {\n sections.push(...collectSections(filterVisibleNodesRecursive(region, visibilityContext), sectionType));\n }\n }\n\n const content = sections\n .map((section) => {\n const props: Record<string, unknown> = \"props\" in section ? (section.props ?? {}) : {};\n const value = propKeys.map((key) => props[key]).find((v) => typeof v === \"string\" && v);\n return typeof value === \"string\" ? value : \"\";\n })\n .filter(Boolean)\n .join(\"\\n\\n\");\n\n if (content) return new Response(content, { headers: TEXT_PLAIN });\n }\n } catch {\n // Fall through to the default\n }\n\n return new Response(defaultContent, { headers: TEXT_PLAIN });\n}\n\n/** Allow everything in production, disallow everything elsewhere. */\nexport function defaultRobotsContent(environment = environmentFromEnv()): string {\n return environment === \"production\" ? \"User-agent: *\\nAllow: /\\n\" : \"User-agent: *\\nDisallow: /\\n\";\n}\n","import type { Metadata } from \"next\";\nimport { notFound, permanentRedirect, redirect } from \"next/navigation\";\nimport type { ReactNode } from \"react\";\nimport type { ComponentRegistry } from \"@botlr/react\";\nimport type { BotlrClient, PublisherScope } from \"@botlr/sdk\";\nimport { environmentFromEnv, scopeFromEnv } from \"./env\";\nimport { buildPageMetadata } from \"./page-metadata\";\nimport { renderCmsPage, renderErrorBox } from \"./render-cms-page\";\nimport { createPageResolver, type ResolvePageOptions, type ResolvedPage } from \"./resolve-page\";\n\nexport interface LocalePageConfig {\n /** CMS locale code, for example `da` or `en`. */\n locale: string;\n /** URL prefix for this locale, for example `/en`. Empty for the root locale. */\n folder?: string;\n /** The client the pages are fetched with; usually one built with `createBotlrFromEnv`. */\n client: BotlrClient;\n componentRegistry: ComponentRegistry;\n /** Overrides on the scope built from the environment. */\n scope?: Partial<PublisherScope>;\n /** The site origin, for canonical and hreflang metadata. */\n siteUrl?: string;\n /** Environment for visibility rules; defaults to `environmentFromEnv()`. */\n environment?: string;\n resolveOptions?: ResolvePageOptions;\n /** Redirect kind for CMS redirects; defaults to `permanent` (308). */\n redirect?: \"permanent\" | \"temporary\";\n /** Page slugs fetched for `generateStaticParams`; defaults to 1000. */\n staticParamsLimit?: number;\n /** Rendered when resolving fails. */\n renderError?: (message: string) => ReactNode;\n}\n\nexport interface LocalePageProps {\n params: Promise<{ slug?: string[] }>;\n searchParams?: Promise<Record<string, string | string[] | undefined>>;\n}\n\n/** The page slug for a catch-all route: the segments joined, or `index`. */\nexport async function pageSlugFromParams(params: LocalePageProps[\"params\"]): Promise<string> {\n const slug = (await params).slug ?? [];\n return slug.length > 0 ? slug.join(\"/\") : \"index\";\n}\n\n/**\n * Everything a static per-locale `[[...slug]]` route needs. The route file\n * keeps only its segment config, which Next must read statically:\n *\n * ```ts\n * // app/(root)/[[...slug]]/page.tsx\n * export const revalidate = false;\n * export const dynamicParams = false;\n * const page = createLocalePage({ locale: \"da\", folder: \"\", client: botlr, componentRegistry });\n * export const generateStaticParams = page.generateStaticParams;\n * export const generateMetadata = page.generateMetadata;\n * export default page.Page;\n * ```\n */\nexport function createLocalePage(config: LocalePageConfig) {\n const { locale, client, componentRegistry } = config;\n const folder = config.folder ? `/${config.folder.replace(/^\\/+|\\/+$/g, \"\")}` : \"\";\n const scope: PublisherScope = scopeFromEnv({ locale, localePrefix: folder.replace(/^\\//, \"\"), ...config.scope });\n const resolvePage = createPageResolver(client, config.resolveOptions);\n const sendRedirect: (url: string) => never = config.redirect === \"temporary\" ? redirect : permanentRedirect;\n\n async function generateStaticParams(): Promise<{ slug?: string[] }[]> {\n const response = await client.getPageSlugList({ locale, limit: config.staticParamsLimit ?? 1000 });\n return (response.data ?? []).map((path) => ({ slug: path ? path.split(\"/\") : [] }));\n }\n\n async function generateMetadata({ params }: LocalePageProps): Promise<Metadata> {\n const resolved = await resolvePage(await pageSlugFromParams(params), locale);\n if (resolved.kind !== \"page\") return {};\n return buildPageMetadata(resolved.pageData, { siteUrl: config.siteUrl, folder });\n }\n\n async function Page({ params }: LocalePageProps) {\n const resolved: ResolvedPage = await resolvePage(await pageSlugFromParams(params), locale);\n\n if (resolved.kind === \"error\") {\n if (config.renderError) return <>{config.renderError(resolved.message)}</>;\n return renderErrorBox(\"Error Loading Page\", resolved.message);\n }\n if (resolved.kind === \"404\") notFound();\n if (resolved.kind === \"redirect\") sendRedirect(`${folder}${resolved.destination}`);\n\n const { pageData } = resolved;\n if (pageData.status !== \"published\") notFound();\n\n return renderCmsPage({\n pageData,\n scope,\n componentRegistry,\n mode: \"published\",\n locale,\n environment: config.environment ?? environmentFromEnv(),\n client,\n });\n }\n\n return { generateStaticParams, generateMetadata, Page, resolvePage, scope };\n}\n\n/**\n * A locale folder that only aliases another, for example `/se/*` to `/sv/*`.\n * Permanently redirects, keeping the rest of the path.\n */\nexport function createLocaleAliasPage(targetFolder: string) {\n return async function AliasPage({ params }: LocalePageProps): Promise<never> {\n const slug = (await params).slug ?? [];\n permanentRedirect(`${targetFolder}/${slug.join(\"/\")}`);\n };\n}\n","type Entry = { count: number; resetAt: number };\n\nconst store = new Map<string, Entry>();\n\nexport interface RateLimitConfig {\n /** Max requests allowed in the window */\n limit: number;\n /** Window size in seconds */\n window: number;\n}\n\nexport interface RateLimitResult {\n allowed: boolean;\n remaining: number;\n /** Epoch ms when the window resets */\n resetAt: number;\n}\n\n/**\n * A fixed-window in-memory rate limit, per server instance. Enough to slow\n * down form and forum spam; not shared across instances or restarts.\n *\n * ```ts\n * const rl = rateLimit(`form:${getClientIp(request)}`, { limit: 5, window: 60 });\n * if (!rl.allowed) return new Response(\"Too many requests\", { status: 429 });\n * ```\n */\nexport function rateLimit(key: string, config: RateLimitConfig): RateLimitResult {\n const now = Date.now();\n const windowMs = config.window * 1000;\n\n let entry = store.get(key);\n\n if (!entry || now >= entry.resetAt) {\n entry = { count: 1, resetAt: now + windowMs };\n store.set(key, entry);\n return { allowed: true, remaining: config.limit - 1, resetAt: entry.resetAt };\n }\n\n entry.count++;\n const allowed = entry.count <= config.limit;\n\n return {\n allowed,\n remaining: Math.max(0, config.limit - entry.count),\n resetAt: entry.resetAt,\n };\n}\n\n/** The client IP from `x-forwarded-for` or `x-real-ip`, or `\"unknown\"`. */\nexport function getClientIp(request: Request): string {\n return (\n request.headers.get(\"x-forwarded-for\")?.split(\",\")[0]?.trim() ||\n request.headers.get(\"x-real-ip\") ||\n \"unknown\"\n );\n}\n","export { getBotlrForRoute, getCachedBotlr, createBotlrFromEnv, type GetBotlrParams } from \"./server\";\nexport { configFromEnv, scopeFromEnv, environmentFromEnv, BOTLR_ENV, DEFAULT_API_URL } from \"./env\";\nexport { localeFromAcceptLanguage } from \"./locale\";\nexport { resolvePage, createPageResolver, type ResolvedPage, type ResolvePageOptions } from \"./resolve-page\";\nexport { buildPageMetadata, type PageMetadataOptions } from \"./page-metadata\";\nexport {\n renderCmsPage,\n prepareTemplate,\n mergeTemplateLayout,\n collectSharedSectionIds,\n type RenderCmsPageOptions,\n} from \"./render-cms-page\";\nexport { themeSettingsToCss, type ThemeSettings, type ThemeColorSet } from \"./theme-css\";\nexport { serveStaticTextFile, defaultRobotsContent, type StaticTextFileOptions } from \"./static-text-file\";\nexport {\n createLocalePage,\n createLocaleAliasPage,\n pageSlugFromParams,\n type LocalePageConfig,\n type LocalePageProps,\n} from \"./locale-page\";\nexport { rateLimit, getClientIp, type RateLimitConfig, type RateLimitResult } from \"./rate-limit\";\n\nexport * from \"@botlr/react\";\nexport type * from \"@botlr/sdk\";\nexport {\n BotlrError,\n BotlrConflictError,\n createBotlrClient,\n getAssetUrl,\n createAssetUrl,\n getAssetId,\n getAssetFilename,\n isAssetData,\n} from \"@botlr/sdk\";\n"],"mappings":"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","names":["cache","filterVisibleNodesRecursive","filterVisibleNodesRecursive","Fragment","jsx","resolvePage","createBotlrClient"]}
|
|
1
|
+
{"version":3,"sources":["../src/server.ts","../src/env.ts","../src/locale.ts","../src/resolve-page.ts","../src/page-metadata.ts","../src/render-cms-page.tsx","../src/theme-css.ts","../src/static-text-file.ts","../src/locale-page.tsx","../src/rate-limit.ts","../src/index.ts"],"sourcesContent":["import { headers } from \"next/headers\";\nimport { cache } from \"react\";\nimport { createBotlrClient, type BotlrClient, type BotlrClientConfig } from \"@botlr/sdk\";\nimport { configFromEnv } from \"./env\";\nimport { localeFromAcceptLanguage } from \"./locale\";\n\nexport type GetBotlrParams = {\n /** The app instance ID. Falls back to `BOTLR_APP_ID`. */\n appId?: string;\n /** Locale override; defaults to the request's `Accept-Language`, then `en`. */\n locale?: string;\n preview?: boolean;\n /** Site token; falls back to `BOTLR_API_KEY`. Omit in dev mode. */\n apiKey?: string;\n /** Preview JWT, forwarded to `/api/resolve` in preview mode. */\n previewToken?: string;\n /** API origin; falls back to `BOTLR_API_URL`. */\n baseUrl?: string;\n fetch?: typeof fetch;\n /** Retry policy for failed reads; see `BotlrClientConfig.retry`. */\n retry?: BotlrClientConfig[\"retry\"];\n};\n\n/**\n * A Botlr client for the current request, for Server Components, route\n * handlers and server actions. Reads the locale from `Accept-Language`\n * unless given.\n *\n * ```ts\n * const botlr = await getBotlrForRoute({ appId: scope.appInstanceId, locale: scope.locale, preview });\n * const pages = await botlr.getPageList({ tags: [\"news\"] });\n * ```\n */\nexport async function getBotlrForRoute(params: GetBotlrParams = {}): Promise<BotlrClient> {\n const h = await headers();\n const locale = params.locale || localeFromAcceptLanguage(h.get(\"accept-language\"));\n return createBotlrClient(\n configFromEnv({\n appId: params.appId,\n apiKey: params.apiKey,\n baseUrl: params.baseUrl,\n previewToken: params.previewToken,\n locale,\n preview: params.preview,\n fetch: params.fetch,\n retry: params.retry,\n }),\n );\n}\n\n/**\n * `getBotlrForRoute` memoised per request with React `cache`, so several\n * components in one render share a client.\n */\nexport const getCachedBotlr: (params?: GetBotlrParams) => Promise<BotlrClient> = cache(getBotlrForRoute);\n\n/**\n * A client from the environment alone, with no request context. For scripts,\n * `generateStaticParams`, instrumentation and anywhere `headers()` is not\n * available.\n */\nexport function createBotlrFromEnv(overrides: Partial<GetBotlrParams> = {}): BotlrClient {\n return createBotlrClient(configFromEnv(overrides));\n}\n","import type { BotlrClientConfig, PublisherScope } from \"@botlr/sdk\";\n\n/** Environment variables the helpers read. All server-side: the API URL is never exposed to the browser. */\nexport const BOTLR_ENV = {\n apiUrl: \"BOTLR_API_URL\",\n apiKey: \"BOTLR_API_KEY\",\n appId: \"BOTLR_APP_ID\",\n pat: \"BOTLR_PAT\",\n orgSlug: \"BOTLR_ORG_SLUG\",\n projectSlug: \"BOTLR_PROJECT_SLUG\",\n appInstanceSlug: \"BOTLR_APP_INSTANCE_SLUG\",\n environment: \"BOTLR_ENVIRONMENT\",\n} as const;\n\nexport const DEFAULT_API_URL = \"http://localhost:8888\";\n\nfunction env(name: string): string | undefined {\n const value = typeof process !== \"undefined\" ? process.env?.[name] : undefined;\n return value === \"\" ? undefined : value;\n}\n\n/**\n * Client config from the environment, with `overrides` winning. Throws when\n * no app id is known, since every Botlr request needs one.\n */\nexport function configFromEnv(overrides: Partial<BotlrClientConfig> = {}): BotlrClientConfig {\n const appId = overrides.appId ?? env(BOTLR_ENV.appId);\n if (!appId) {\n throw new Error(`@botlr/next: pass appId or set ${BOTLR_ENV.appId}`);\n }\n return {\n ...overrides,\n baseUrl: overrides.baseUrl ?? env(BOTLR_ENV.apiUrl) ?? DEFAULT_API_URL,\n appId,\n apiKey: overrides.apiKey ?? env(BOTLR_ENV.apiKey),\n personalToken: overrides.personalToken ?? env(BOTLR_ENV.pat),\n };\n}\n\n/**\n * The deployment environment used by visibility rules: `BOTLR_ENVIRONMENT`,\n * then `ENVIRONMENT`, then `NODE_ENV`, then `fallback`.\n */\nexport function environmentFromEnv(fallback = \"production\"): string {\n return env(BOTLR_ENV.environment) ?? env(\"ENVIRONMENT\") ?? env(\"NODE_ENV\") ?? fallback;\n}\n\n/**\n * The `PublisherScope` for a locale from `BOTLR_ORG_SLUG`, `BOTLR_PROJECT_SLUG`,\n * `BOTLR_APP_INSTANCE_SLUG` and `BOTLR_APP_ID`, with `overrides` winning.\n * Missing slugs become empty strings rather than errors, since only asset\n * URLs need them.\n */\nexport function scopeFromEnv(overrides: Partial<PublisherScope> & Pick<PublisherScope, \"locale\">): PublisherScope {\n return {\n orgSlug: env(BOTLR_ENV.orgSlug) ?? \"\",\n projectSlug: env(BOTLR_ENV.projectSlug) ?? \"\",\n appInstanceSlug: env(BOTLR_ENV.appInstanceSlug) ?? \"\",\n appInstanceId: env(BOTLR_ENV.appId) ?? \"\",\n ...overrides,\n };\n}\n","/** The first language of an `Accept-Language` value, or `fallback`. */\nexport function localeFromAcceptLanguage(header: string | null | undefined, fallback = \"en\"): string {\n if (!header) return fallback;\n const first = header.split(\",\")[0]?.split(\";\")[0]?.trim();\n return first || fallback;\n}\n","import { cache } from \"react\";\nimport type { BotlrClient, InlineSectionInstance, PageResponse } from \"@botlr/sdk\";\n\nexport type ResolvedPage =\n | { kind: \"page\"; pageData: PageResponse }\n | { kind: \"redirect\"; destination: string }\n | { kind: \"404\" }\n | { kind: \"error\"; message: string; cause: unknown };\n\nexport interface ResolvePageOptions {\n /**\n * Non-page content types rendered through a section: the resolved content\n * is prepended to the `main` region as an inline section of the mapped\n * type. Defaults to `{ article: \"article\" }`.\n */\n contentSections?: Record<string, string>;\n}\n\nconst DEFAULT_CONTENT_SECTIONS: Record<string, string> = { article: \"article\" };\n\n/**\n * Resolves a slug to a page through `/api/resolve`, folding redirects, 404s\n * and errors into one result and injecting article content into the layout.\n * Not cached; see `createPageResolver`.\n */\nexport async function resolvePage(\n client: BotlrClient,\n slug: string,\n locale?: string,\n options: ResolvePageOptions = {},\n): Promise<ResolvedPage> {\n try {\n const result = await client.resolveUrl(slug, locale);\n\n if (result.kind === \"404\") return { kind: \"404\" };\n if (result.kind === \"redirect\" && result.redirect) {\n return { kind: \"redirect\", destination: result.redirect.destination };\n }\n if (!result.data) return { kind: \"404\" };\n\n const data = result.data;\n const layout = data.layout ?? {};\n\n const content = data.content ?? {};\n const contentType: string | null = content[\"type\"] ?? data.type ?? null;\n if (contentType && contentType !== \"page\") {\n const sectionType = (options.contentSections ?? DEFAULT_CONTENT_SECTIONS)[contentType];\n if (sectionType) {\n const section: InlineSectionInstance = {\n _key: `${sectionType}-${content[\"id\"] ?? data.id}`,\n mode: \"inline\",\n sectionType,\n props: content,\n status: \"published\",\n };\n layout[\"main\"] = [section, ...(layout[\"main\"] ?? [])];\n } else {\n console.warn(\"Unknown content type:\", contentType);\n }\n }\n\n return {\n kind: \"page\",\n pageData: {\n id: data.id,\n name: data.name,\n slug: data.slug || \"\",\n full_slug: data.full_slug || \"\",\n layout,\n status: data.status,\n theme: data.theme,\n page_template: data.page_template,\n seo_meta: data.seo_meta,\n alternate_translations: result.alternate_translations ?? [],\n breadcrumbs: result.breadcrumbs ?? [],\n },\n };\n } catch (error) {\n return {\n kind: \"error\",\n message: error instanceof Error ? error.message : \"Unknown error\",\n cause: error,\n };\n }\n}\n\n/**\n * `resolvePage` bound to a client and memoised per request with React\n * `cache`, so `generateMetadata` and the page share one fetch.\n *\n * ```ts\n * const resolvePage = createPageResolver(botlr);\n * const resolved = await resolvePage(\"about/team\", \"en\");\n * ```\n */\nexport function createPageResolver(\n client: BotlrClient,\n options: ResolvePageOptions = {},\n): (slug: string, locale?: string) => Promise<ResolvedPage> {\n return cache((slug: string, locale?: string) => resolvePage(client, slug, locale, options));\n}\n","import type { Metadata } from \"next\";\nimport type { PageResponse } from \"@botlr/sdk\";\n\nexport interface PageMetadataOptions {\n /**\n * The site origin, for example `https://www.example.com`. Needed for the\n * canonical URL and hreflang alternates; without it only an explicit\n * `seo_meta.canonical` is emitted.\n */\n siteUrl?: string;\n /** URL prefix of the page's locale, for example `/en`. Empty for the root locale. */\n folder?: string;\n}\n\n/**\n * Next.js `Metadata` from a page's `seo_meta`: title, description, keywords,\n * Open Graph, Twitter, robots, canonical and hreflang alternates.\n */\nexport function buildPageMetadata(pageData: PageResponse, options: PageMetadataOptions = {}): Metadata {\n const seo = pageData.seo_meta || {};\n const title = seo.title || pageData.name;\n const alternates = pageData.alternate_translations || [];\n const siteUrl = options.siteUrl?.replace(/\\/+$/, \"\");\n const folder = options.folder ? `/${options.folder.replace(/^\\/+|\\/+$/g, \"\")}` : \"\";\n\n const metadata: Metadata = { title };\n\n if (seo.description) metadata.description = seo.description;\n if (seo.keywords) metadata.keywords = seo.keywords;\n\n const ogImage = seo.ogImage?.url || seo.ogImage?.filename;\n const description = seo.ogDescription || seo.description;\n metadata.openGraph = {\n title: seo.ogTitle || title,\n ...(description ? { description } : {}),\n ...(seo.ogType ? { type: seo.ogType } : {}),\n ...(ogImage ? { images: [ogImage] } : {}),\n };\n\n if (seo.twitterCard) {\n metadata.twitter = {\n card: seo.twitterCard,\n title: seo.ogTitle || title,\n ...(description ? { description } : {}),\n ...(ogImage ? { images: [ogImage] } : {}),\n };\n }\n\n if (seo.noindex !== undefined || seo.nofollow !== undefined || seo.robots) {\n metadata.robots = seo.robots ?? { index: !seo.noindex, follow: !seo.nofollow };\n }\n\n if (seo.canonical) {\n metadata.alternates = { canonical: seo.canonical };\n } else if (siteUrl) {\n const canonical = pageData.full_slug\n ? `${siteUrl}${folder}/${pageData.full_slug}`\n : `${siteUrl}${folder}`;\n metadata.alternates = { canonical };\n }\n\n if (siteUrl && alternates.length > 0) {\n const languages: Record<string, string> = {};\n for (const alt of alternates) {\n languages[alt.locale] = `${siteUrl}${alt.full_slug}`;\n }\n metadata.alternates = { ...metadata.alternates, languages };\n }\n\n return metadata;\n}\n","import type { JSX, ReactNode } from \"react\";\nimport parse, { type HTMLReactParserOptions } from \"html-react-parser\";\nimport {\n SectionRenderer,\n filterVisibleNodesRecursive,\n type ComponentRegistry,\n type VisibilityContext,\n} from \"@botlr/react\";\nimport type { BotlrClient, LayoutNode, PageResponse, PageScope, PublisherScope } from \"@botlr/sdk\";\nimport { environmentFromEnv } from \"./env\";\nimport { themeSettingsToCss } from \"./theme-css\";\n\ntype Layout = Record<string, LayoutNode[]>;\n\nexport interface RenderCmsPageOptions {\n pageData: PageResponse;\n scope: PublisherScope;\n /** Section type to component map used for every region. */\n componentRegistry: ComponentRegistry;\n /** Defaults to `\"published\"`; `\"draft\"` also renders draft sections. */\n mode?: \"draft\" | \"published\";\n /** Defaults to `scope.locale`. */\n locale?: string;\n /** Environment for visibility rules; defaults to `environmentFromEnv()`. */\n environment?: string;\n /** Used to fetch shared sections unless `fetchSharedSection` is given. */\n client?: BotlrClient;\n /** Fetch a shared section's page by id. Defaults to `client.getPageBySlug(id, locale)`. */\n fetchSharedSection?: (id: string) => Promise<{ layout?: Partial<Layout> } | null>;\n /** Show a fixed \"draft/published preview\" badge; for preview routes only. */\n showModeBadge?: boolean;\n /** Rendered when the template cannot be parsed. */\n renderError?: (error: unknown) => ReactNode;\n}\n\nconst DEFAULT_TEMPLATE = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>__PAGE_TITLE__</title>\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }\n </style>\n __THEME_CSS__\n</head>\n<body>\n <main>\n __REGION_MAIN__\n </main>\n</body>\n</html>`;\n\nfunction extractBodyContent(html: string): string {\n const bodyMatch = html.match(/<body[^>]*>([\\s\\S]*)<\\/body>/i);\n return bodyMatch?.[1] ? bodyMatch[1].trim() : html;\n}\n\n/** Ids of every shared section in the layout, grid columns included. */\nexport function collectSharedSectionIds(nodes: LayoutNode[]): string[] {\n const ids: string[] = [];\n for (const node of nodes) {\n if (\"mode\" in node && node.mode === \"shared\" && node.sharedSectionId) {\n ids.push(node.sharedSectionId);\n }\n if (\"type\" in node && node.type === \"grid-container\" && Array.isArray(node.columns)) {\n for (const column of node.columns) {\n if (Array.isArray(column.sections)) ids.push(...collectSharedSectionIds(column.sections));\n }\n }\n }\n return ids;\n}\n\n/**\n * The template's `{{...}}` placeholders resolved: title, lang, theme css,\n * `{{{REGION}}}` markers turned into text markers for the parser, and the\n * body extracted.\n */\nexport function prepareTemplate(templateHtml: string, pageData: PageResponse, templateCss: string, locale: string): string {\n let prepared = templateHtml\n .replace(/\\{\\{page_title\\}\\}/g, pageData.name)\n .replace(/\\{\\{page_lang\\}\\}/g, locale)\n .replace(/\\{\\{page_description\\}\\}/g, \"\");\n\n prepared = prepared.replace(/\\{\\{\\{theme_css\\}\\}\\}/g, templateCss ? `<style>${templateCss}</style>` : \"\");\n prepared = prepared.replace(/\\{\\{\\{([A-Z_]+)\\}\\}\\}/g, \"__REGION_$1__\");\n prepared = prepared.replace(/\\{\\{#if\\s+([A-Z_]+)\\}\\}/g, \"\");\n prepared = prepared.replace(/\\{\\{\\/if\\}\\}/g, \"\");\n\n return extractBodyContent(prepared);\n}\n\n/** Template sections first, then the page's own, per region. */\nexport function mergeTemplateLayout(pageData: PageResponse): Layout {\n const merged: Layout = {};\n const regions = new Set([...Object.keys(pageData.layout), ...Object.keys(pageData.page_template?.layout ?? {})]);\n for (const region of regions) {\n merged[region] = [...(pageData.page_template?.layout?.[region] ?? []), ...(pageData.layout[region] ?? [])];\n }\n return merged;\n}\n\nconst errorBox = (heading: string, message: string): JSX.Element => (\n <div style={{ display: \"flex\", alignItems: \"center\", justifyContent: \"center\", minHeight: \"100vh\" }}>\n <div style={{ textAlign: \"center\" }}>\n <h1 style={{ fontSize: \"1.5rem\", fontWeight: 700, marginBottom: \"0.5rem\" }}>{heading}</h1>\n <p style={{ opacity: 0.7 }}>{message}</p>\n </div>\n </div>\n);\n\n/**\n * Renders a resolved page: picks the page template (page, then theme, then a\n * built-in default), merges template and page layouts, applies visibility\n * rules, fetches shared sections and mounts a `SectionRenderer` at every\n * `{{{REGION}}}` marker.\n */\nexport async function renderCmsPage({\n pageData,\n scope,\n componentRegistry,\n mode = \"published\",\n locale = scope.locale,\n environment = environmentFromEnv(),\n client,\n fetchSharedSection,\n showModeBadge = false,\n renderError,\n}: RenderCmsPageOptions): Promise<JSX.Element> {\n let templateHtml: string;\n let templateCss = \"\";\n\n if (pageData.page_template?.template_html) {\n templateHtml = pageData.page_template.template_html;\n templateCss = pageData.page_template.template_css || \"\";\n } else if (pageData.theme?.default_template_html) {\n templateHtml = pageData.theme.default_template_html;\n templateCss = pageData.theme.default_template_css || \"\";\n } else {\n templateHtml = DEFAULT_TEMPLATE;\n }\n\n const preparedTemplate = prepareTemplate(templateHtml, pageData, templateCss, locale);\n\n const pageScope: PageScope = {\n id: pageData.id,\n kind: \"page\",\n name: pageData.name,\n slug: pageData.slug || \"\",\n breadcrumbs: pageData.breadcrumbs || [],\n };\n\n const visibilityContext: VisibilityContext = {\n pageId: pageData.id,\n currentPath: \"/\" + (pageData.slug || \"\"),\n locale,\n environment,\n };\n const filteredLayout: Layout = {};\n for (const [region, sections] of Object.entries(mergeTemplateLayout(pageData))) {\n filteredLayout[region] = filterVisibleNodesRecursive(sections, visibilityContext);\n }\n\n const fetchShared =\n fetchSharedSection ??\n (client ? (id: string) => client.getPageBySlug(id, locale) : undefined);\n const sharedSectionIds = Object.values(filteredLayout).flatMap(collectSharedSectionIds);\n const sharedSectionsMap = new Map<string, LayoutNode[]>();\n\n if (sharedSectionIds.length > 0) {\n if (!fetchShared) {\n throw new Error(\"@botlr/next: renderCmsPage needs `client` or `fetchSharedSection` to render shared sections\");\n }\n const results = await Promise.all(\n [...new Set(sharedSectionIds)].map(async (id) => {\n try {\n const data = await fetchShared(id);\n const main = data?.layout?.[\"main\"];\n if (Array.isArray(main) && main.length > 0) return { id, sections: main };\n } catch (err) {\n console.error(`Error fetching shared section ${id}:`, err);\n }\n return null;\n }),\n );\n for (const result of results) {\n if (result) sharedSectionsMap.set(result.id, result.sections);\n }\n }\n\n const parseOptions: HTMLReactParserOptions = {\n replace: (domNode) => {\n if (!(\"data\" in domNode) || typeof domNode.data !== \"string\") return;\n const regionMatch = domNode.data.match(/__REGION_([A-Z_]+)__/);\n if (!regionMatch?.[1]) return;\n\n const regionKey = regionMatch[1].toLowerCase();\n const regionSections = filteredLayout[regionKey] ?? [];\n if (regionSections.length === 0) return <></>;\n\n const templateSectionKeys = new Set(\n (pageData.page_template?.layout?.[regionKey] ?? []).map((section) => section._key),\n );\n\n return (\n <>\n {regionSections.map((section) => (\n <SectionRenderer\n key={section._key}\n section={section}\n componentRegistry={componentRegistry}\n mode={mode}\n sharedSectionsData={sharedSectionsMap}\n scope={scope}\n pageScope={pageScope}\n fromTemplate={templateSectionKeys.has(section._key)}\n />\n ))}\n </>\n );\n },\n };\n\n const themeSettingsCss = themeSettingsToCss(pageData.theme?.settings);\n\n try {\n const renderedContent = parse(preparedTemplate, parseOptions);\n\n return (\n <>\n {themeSettingsCss && <style dangerouslySetInnerHTML={{ __html: themeSettingsCss }} />}\n {templateCss && <style dangerouslySetInnerHTML={{ __html: templateCss }} />}\n {renderedContent}\n {showModeBadge && (\n <div\n style={{\n position: \"fixed\",\n bottom: \"1rem\",\n right: \"1rem\",\n padding: \"0.5rem 1rem\",\n borderRadius: \"0.5rem\",\n background: \"#111\",\n color: \"#fff\",\n fontSize: \"0.875rem\",\n fontWeight: 500,\n boxShadow: \"0 4px 12px rgba(0,0,0,0.2)\",\n zIndex: 50,\n }}\n >\n {mode === \"draft\" ? \"Draft preview\" : \"Published preview\"}\n </div>\n )}\n </>\n );\n } catch (error) {\n console.error(\"Error rendering template:\", error);\n if (renderError) return <>{renderError(error)}</>;\n return errorBox(\"Template Error\", error instanceof Error ? error.message : \"Failed to render template\");\n }\n}\n\nexport { errorBox as renderErrorBox };\n","export interface ThemeColorSet {\n primary?: string;\n primaryForeground?: string;\n secondary?: string;\n secondaryForeground?: string;\n accent?: string;\n accentForeground?: string;\n background?: string;\n foreground?: string;\n muted?: string;\n mutedForeground?: string;\n border?: string;\n input?: string;\n ring?: string;\n destructive?: string;\n destructiveForeground?: string;\n success?: string;\n successForeground?: string;\n warning?: string;\n warningForeground?: string;\n info?: string;\n infoForeground?: string;\n}\n\nexport interface ThemeSettings {\n colors?: { light?: ThemeColorSet; dark?: ThemeColorSet };\n typography?: {\n fontFamily?: {\n heading?: string;\n body?: string;\n ui?: string;\n code?: string;\n sans?: string;\n serif?: string;\n mono?: string;\n };\n fontSize?: {\n h1?: string; h2?: string; h3?: string; h4?: string; h5?: string; h6?: string;\n xs?: string; sm?: string; base?: string; lg?: string; xl?: string;\n \"2xl\"?: string; \"3xl\"?: string; \"4xl\"?: string;\n };\n fontWeight?: { normal?: string; medium?: string; semibold?: string; bold?: string };\n lineHeight?: { tight?: string; normal?: string; relaxed?: string };\n };\n spacing?: { xs?: string; sm?: string; md?: string; lg?: string; xl?: string; \"2xl\"?: string };\n borderRadius?: { none?: string; sm?: string; md?: string; lg?: string; xl?: string; full?: string };\n shadows?: { sm?: string; md?: string; lg?: string; xl?: string };\n layout?: {\n containerMaxWidth?: string;\n sidebarWidth?: string;\n headerHeight?: string;\n footerHeight?: string;\n };\n}\n\nconst COLOR_KEYS: (keyof ThemeColorSet)[] = [\n \"primary\", \"primaryForeground\", \"secondary\", \"secondaryForeground\", \"accent\", \"accentForeground\",\n \"background\", \"foreground\", \"muted\", \"mutedForeground\", \"border\", \"input\", \"ring\",\n \"destructive\", \"destructiveForeground\", \"success\", \"successForeground\",\n \"warning\", \"warningForeground\", \"info\", \"infoForeground\",\n];\n\n/** Groups of common variables, in output order: [settings path, css prefix, keys]. */\nconst COMMON_GROUPS: [string[], string, string[]][] = [\n [[\"typography\", \"fontFamily\"], \"--font\", [\"heading\", \"body\", \"ui\", \"code\", \"sans\", \"serif\", \"mono\"]],\n [[\"typography\", \"fontSize\"], \"--font-size\", [\"h1\", \"h2\", \"h3\", \"h4\", \"h5\", \"h6\", \"xs\", \"sm\", \"base\", \"lg\", \"xl\", \"2xl\", \"3xl\", \"4xl\"]],\n [[\"typography\", \"fontWeight\"], \"--font-weight\", [\"normal\", \"medium\", \"semibold\", \"bold\"]],\n [[\"typography\", \"lineHeight\"], \"--line-height\", [\"tight\", \"normal\", \"relaxed\"]],\n [[\"spacing\"], \"--spacing\", [\"xs\", \"sm\", \"md\", \"lg\", \"xl\", \"2xl\"]],\n [[\"borderRadius\"], \"--radius\", [\"none\", \"sm\", \"md\", \"lg\", \"xl\", \"full\"]],\n [[\"shadows\"], \"--shadow\", [\"sm\", \"md\", \"lg\", \"xl\"]],\n [[\"layout\"], \"-\", [\"containerMaxWidth\", \"sidebarWidth\", \"headerHeight\", \"footerHeight\"]],\n];\n\nconst kebab = (key: string): string => key.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);\n\nfunction colorVars(colors: ThemeColorSet | undefined): string[] {\n if (!colors) return [];\n const vars: string[] = [];\n for (const key of COLOR_KEYS) {\n const value = colors[key];\n if (value) vars.push(`--color-${kebab(key)}: ${value};`);\n }\n return vars;\n}\n\nfunction pick(settings: Record<string, any>, path: string[]): Record<string, string> | undefined {\n let current: any = settings;\n for (const segment of path) {\n current = current?.[segment];\n if (!current) return undefined;\n }\n return current;\n}\n\n/**\n * Theme settings as CSS custom properties: common variables and light colors\n * on `:root`, dark colors under `prefers-color-scheme: dark` and `.dark`.\n */\nexport function themeSettingsToCss(settings?: ThemeSettings | Record<string, any>): string {\n if (!settings) return \"\";\n\n const s = settings as ThemeSettings;\n const lightVars = colorVars(s.colors?.light);\n const darkVars = colorVars(s.colors?.dark);\n const commonVars: string[] = [];\n\n for (const [path, prefix, keys] of COMMON_GROUPS) {\n const group = pick(settings, path);\n if (!group) continue;\n for (const key of keys) {\n const value = group[key];\n if (value) commonVars.push(`${prefix}-${kebab(key)}: ${value};`);\n }\n }\n\n const cssBlocks: string[] = [];\n\n if (commonVars.length > 0) {\n cssBlocks.push(`:root {\\n ${commonVars.join(\"\\n \")}\\n}`);\n }\n\n if (lightVars.length > 0) {\n cssBlocks.push(`:root {\\n ${lightVars.join(\"\\n \")}\\n}`);\n }\n\n if (darkVars.length > 0) {\n cssBlocks.push(`@media (prefers-color-scheme: dark) {\\n :root {\\n ${darkVars.join(\"\\n \")}\\n }\\n}`);\n cssBlocks.push(`.dark {\\n ${darkVars.join(\"\\n \")}\\n}`);\n }\n\n return cssBlocks.join(\"\\n\\n\");\n}\n","import { filterVisibleNodesRecursive, type VisibilityContext } from \"@botlr/react\";\nimport type { BotlrClient, LayoutNode } from \"@botlr/sdk\";\nimport { environmentFromEnv } from \"./env\";\n\nexport interface StaticTextFileOptions {\n /** Defaults to the client's locale, then `en`. */\n locale?: string;\n /** Environment for visibility rules; defaults to `environmentFromEnv()`. */\n environment?: string;\n /** The section type whose content is served; defaults to `text`. */\n sectionType?: string;\n /** Section props tried in order for the text; defaults to `content`, `body`, `text`. */\n propKeys?: string[];\n}\n\nfunction collectSections(nodes: LayoutNode[], sectionType: string): LayoutNode[] {\n const result: LayoutNode[] = [];\n for (const node of nodes) {\n if (\"mode\" in node && node.mode === \"inline\" && node.sectionType === sectionType) {\n result.push(node);\n } else if (\"type\" in node && node.type === \"grid-container\" && Array.isArray(node.columns)) {\n for (const column of node.columns) {\n result.push(...collectSections(column.sections ?? [], sectionType));\n }\n }\n }\n return result;\n}\n\nconst TEXT_PLAIN = { \"Content-Type\": \"text/plain; charset=utf-8\" };\n\n/**\n * A CMS-managed plain-text file such as `robots.txt`, `humans.txt` or\n * `llms.txt`: the page at `slug`, its visible text sections concatenated.\n * Serves `defaultContent` when the page has none or cannot be fetched.\n *\n * ```ts\n * // app/robots.txt/route.ts\n * export const GET = () => serveStaticTextFile(botlr, \"robots.txt\", defaultRobotsContent());\n * ```\n */\nexport async function serveStaticTextFile(\n client: BotlrClient,\n slug: string,\n defaultContent: string,\n options: StaticTextFileOptions = {},\n): Promise<Response> {\n const locale = options.locale ?? client.config.locale ?? \"en\";\n const environment = options.environment ?? environmentFromEnv();\n const sectionType = options.sectionType ?? \"text\";\n const propKeys = options.propKeys ?? [\"content\", \"body\", \"text\"];\n\n try {\n const page = await client.getPageBySlug(slug, locale);\n if (page?.layout) {\n const visibilityContext: VisibilityContext = { locale, environment };\n const sections: LayoutNode[] = [];\n for (const region of Object.values(page.layout)) {\n if (Array.isArray(region)) {\n sections.push(...collectSections(filterVisibleNodesRecursive(region, visibilityContext), sectionType));\n }\n }\n\n const content = sections\n .map((section) => {\n const props: Record<string, unknown> = \"props\" in section ? (section.props ?? {}) : {};\n const value = propKeys.map((key) => props[key]).find((v) => typeof v === \"string\" && v);\n return typeof value === \"string\" ? value : \"\";\n })\n .filter(Boolean)\n .join(\"\\n\\n\");\n\n if (content) return new Response(content, { headers: TEXT_PLAIN });\n }\n } catch {\n // Fall through to the default\n }\n\n return new Response(defaultContent, { headers: TEXT_PLAIN });\n}\n\n/** Allow everything in production, disallow everything elsewhere. */\nexport function defaultRobotsContent(environment = environmentFromEnv()): string {\n return environment === \"production\" ? \"User-agent: *\\nAllow: /\\n\" : \"User-agent: *\\nDisallow: /\\n\";\n}\n","import type { Metadata } from \"next\";\nimport { notFound, permanentRedirect, redirect } from \"next/navigation\";\nimport type { ReactNode } from \"react\";\nimport type { ComponentRegistry } from \"@botlr/react\";\nimport type { BotlrClient, PublisherScope } from \"@botlr/sdk\";\nimport { environmentFromEnv, scopeFromEnv } from \"./env\";\nimport { buildPageMetadata } from \"./page-metadata\";\nimport { renderCmsPage, renderErrorBox } from \"./render-cms-page\";\nimport { createPageResolver, type ResolvePageOptions, type ResolvedPage } from \"./resolve-page\";\n\nexport interface LocalePageConfig {\n /** CMS locale code, for example `da` or `en`. */\n locale: string;\n /** URL prefix for this locale, for example `/en`. Empty for the root locale. */\n folder?: string;\n /** The client the pages are fetched with; usually one built with `createBotlrFromEnv`. */\n client: BotlrClient;\n componentRegistry: ComponentRegistry;\n /** Overrides on the scope built from the environment. */\n scope?: Partial<PublisherScope>;\n /** The site origin, for canonical and hreflang metadata. */\n siteUrl?: string;\n /** Environment for visibility rules; defaults to `environmentFromEnv()`. */\n environment?: string;\n resolveOptions?: ResolvePageOptions;\n /** Redirect kind for CMS redirects; defaults to `permanent` (308). */\n redirect?: \"permanent\" | \"temporary\";\n /** Page slugs fetched for `generateStaticParams`; defaults to 1000. */\n staticParamsLimit?: number;\n /**\n * What happens when resolving a page throws (after the client's retries).\n * `throw` rethrows, which fails `next build` and gives a 500 at request\n * time; `render` shows an error box in place of the page. Defaults to\n * `render` when `NODE_ENV` is `development` and `throw` otherwise.\n */\n resolveErrors?: \"throw\" | \"render\";\n /** Rendered in place of the page when `resolveErrors` is `render`. */\n renderError?: (message: string) => ReactNode;\n}\n\nexport interface LocalePageProps {\n params: Promise<{ slug?: string[] }>;\n searchParams?: Promise<Record<string, string | string[] | undefined>>;\n}\n\n/** The page slug for a catch-all route: the segments joined, or `index`. */\nexport async function pageSlugFromParams(params: LocalePageProps[\"params\"]): Promise<string> {\n const slug = (await params).slug ?? [];\n return slug.length > 0 ? slug.join(\"/\") : \"index\";\n}\n\n/**\n * Everything a static per-locale `[[...slug]]` route needs. The route file\n * keeps only its segment config, which Next must read statically:\n *\n * ```ts\n * // app/(root)/[[...slug]]/page.tsx\n * export const revalidate = false;\n * export const dynamicParams = false;\n * const page = createLocalePage({ locale: \"da\", folder: \"\", client: botlr, componentRegistry });\n * export const generateStaticParams = page.generateStaticParams;\n * export const generateMetadata = page.generateMetadata;\n * export default page.Page;\n * ```\n */\nexport function createLocalePage(config: LocalePageConfig) {\n const { locale, client, componentRegistry } = config;\n const folder = config.folder ? `/${config.folder.replace(/^\\/+|\\/+$/g, \"\")}` : \"\";\n const scope: PublisherScope = scopeFromEnv({ locale, localePrefix: folder.replace(/^\\//, \"\"), ...config.scope });\n const resolvePage = createPageResolver(client, config.resolveOptions);\n const sendRedirect: (url: string) => never = config.redirect === \"temporary\" ? redirect : permanentRedirect;\n\n async function generateStaticParams(): Promise<{ slug?: string[] }[]> {\n const response = await client.getPageSlugList({ locale, limit: config.staticParamsLimit ?? 1000 });\n return (response.data ?? []).map((path) => ({ slug: path ? path.split(\"/\") : [] }));\n }\n\n async function generateMetadata({ params }: LocalePageProps): Promise<Metadata> {\n const resolved = await resolvePage(await pageSlugFromParams(params), locale);\n if (resolved.kind !== \"page\") return {};\n return buildPageMetadata(resolved.pageData, { siteUrl: config.siteUrl, folder });\n }\n\n async function Page({ params }: LocalePageProps) {\n const slug = await pageSlugFromParams(params);\n const resolved: ResolvedPage = await resolvePage(slug, locale);\n\n if (resolved.kind === \"error\") {\n const mode = config.resolveErrors ?? (process.env.NODE_ENV === \"development\" ? \"render\" : \"throw\");\n if (mode === \"throw\") {\n throw new Error(`@botlr/next: could not resolve \"${slug}\" (${locale}): ${resolved.message}`, {\n cause: resolved.cause,\n });\n }\n if (config.renderError) return <>{config.renderError(resolved.message)}</>;\n return renderErrorBox(\"Error Loading Page\", resolved.message);\n }\n if (resolved.kind === \"404\") notFound();\n if (resolved.kind === \"redirect\") sendRedirect(`${folder}${resolved.destination}`);\n\n const { pageData } = resolved;\n if (pageData.status !== \"published\") notFound();\n\n return renderCmsPage({\n pageData,\n scope,\n componentRegistry,\n mode: \"published\",\n locale,\n environment: config.environment ?? environmentFromEnv(),\n client,\n });\n }\n\n return { generateStaticParams, generateMetadata, Page, resolvePage, scope };\n}\n\n/**\n * A locale folder that only aliases another, for example `/se/*` to `/sv/*`.\n * Permanently redirects, keeping the rest of the path.\n */\nexport function createLocaleAliasPage(targetFolder: string) {\n return async function AliasPage({ params }: LocalePageProps): Promise<never> {\n const slug = (await params).slug ?? [];\n permanentRedirect(`${targetFolder}/${slug.join(\"/\")}`);\n };\n}\n","type Entry = { count: number; resetAt: number };\n\nconst store = new Map<string, Entry>();\n\nexport interface RateLimitConfig {\n /** Max requests allowed in the window */\n limit: number;\n /** Window size in seconds */\n window: number;\n}\n\nexport interface RateLimitResult {\n allowed: boolean;\n remaining: number;\n /** Epoch ms when the window resets */\n resetAt: number;\n}\n\n/**\n * A fixed-window in-memory rate limit, per server instance. Enough to slow\n * down form and forum spam; not shared across instances or restarts.\n *\n * ```ts\n * const rl = rateLimit(`form:${getClientIp(request)}`, { limit: 5, window: 60 });\n * if (!rl.allowed) return new Response(\"Too many requests\", { status: 429 });\n * ```\n */\nexport function rateLimit(key: string, config: RateLimitConfig): RateLimitResult {\n const now = Date.now();\n const windowMs = config.window * 1000;\n\n let entry = store.get(key);\n\n if (!entry || now >= entry.resetAt) {\n entry = { count: 1, resetAt: now + windowMs };\n store.set(key, entry);\n return { allowed: true, remaining: config.limit - 1, resetAt: entry.resetAt };\n }\n\n entry.count++;\n const allowed = entry.count <= config.limit;\n\n return {\n allowed,\n remaining: Math.max(0, config.limit - entry.count),\n resetAt: entry.resetAt,\n };\n}\n\n/** The client IP from `x-forwarded-for` or `x-real-ip`, or `\"unknown\"`. */\nexport function getClientIp(request: Request): string {\n return (\n request.headers.get(\"x-forwarded-for\")?.split(\",\")[0]?.trim() ||\n request.headers.get(\"x-real-ip\") ||\n \"unknown\"\n );\n}\n","export { getBotlrForRoute, getCachedBotlr, createBotlrFromEnv, type GetBotlrParams } from \"./server\";\nexport { configFromEnv, scopeFromEnv, environmentFromEnv, BOTLR_ENV, DEFAULT_API_URL } from \"./env\";\nexport { localeFromAcceptLanguage } from \"./locale\";\nexport { resolvePage, createPageResolver, type ResolvedPage, type ResolvePageOptions } from \"./resolve-page\";\nexport { buildPageMetadata, type PageMetadataOptions } from \"./page-metadata\";\nexport {\n renderCmsPage,\n prepareTemplate,\n mergeTemplateLayout,\n collectSharedSectionIds,\n type RenderCmsPageOptions,\n} from \"./render-cms-page\";\nexport { themeSettingsToCss, type ThemeSettings, type ThemeColorSet } from \"./theme-css\";\nexport { serveStaticTextFile, defaultRobotsContent, type StaticTextFileOptions } from \"./static-text-file\";\nexport {\n createLocalePage,\n createLocaleAliasPage,\n pageSlugFromParams,\n type LocalePageConfig,\n type LocalePageProps,\n} from \"./locale-page\";\nexport { rateLimit, getClientIp, type RateLimitConfig, type RateLimitResult } from \"./rate-limit\";\n\nexport * from \"@botlr/react\";\nexport type * from \"@botlr/sdk\";\nexport {\n BotlrError,\n BotlrConflictError,\n createBotlrClient,\n getAssetUrl,\n createAssetUrl,\n getAssetId,\n getAssetFilename,\n isAssetData,\n} from \"@botlr/sdk\";\n"],"mappings":";AAAA,SAAS,eAAe;AACxB,SAAS,aAAa;AACtB,SAAS,yBAAmE;;;ACCrE,IAAM,YAAY;AAAA,EACvB,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,KAAK;AAAA,EACL,SAAS;AAAA,EACT,aAAa;AAAA,EACb,iBAAiB;AAAA,EACjB,aAAa;AACf;AAEO,IAAM,kBAAkB;AAE/B,SAAS,IAAI,MAAkC;AAC7C,QAAM,QAAQ,OAAO,YAAY,cAAc,QAAQ,MAAM,IAAI,IAAI;AACrE,SAAO,UAAU,KAAK,SAAY;AACpC;AAMO,SAAS,cAAc,YAAwC,CAAC,GAAsB;AAC3F,QAAM,QAAQ,UAAU,SAAS,IAAI,UAAU,KAAK;AACpD,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,kCAAkC,UAAU,KAAK,EAAE;AAAA,EACrE;AACA,SAAO;AAAA,IACL,GAAG;AAAA,IACH,SAAS,UAAU,WAAW,IAAI,UAAU,MAAM,KAAK;AAAA,IACvD;AAAA,IACA,QAAQ,UAAU,UAAU,IAAI,UAAU,MAAM;AAAA,IAChD,eAAe,UAAU,iBAAiB,IAAI,UAAU,GAAG;AAAA,EAC7D;AACF;AAMO,SAAS,mBAAmB,WAAW,cAAsB;AAClE,SAAO,IAAI,UAAU,WAAW,KAAK,IAAI,aAAa,KAAK,IAAI,UAAU,KAAK;AAChF;AAQO,SAAS,aAAa,WAAqF;AAChH,SAAO;AAAA,IACL,SAAS,IAAI,UAAU,OAAO,KAAK;AAAA,IACnC,aAAa,IAAI,UAAU,WAAW,KAAK;AAAA,IAC3C,iBAAiB,IAAI,UAAU,eAAe,KAAK;AAAA,IACnD,eAAe,IAAI,UAAU,KAAK,KAAK;AAAA,IACvC,GAAG;AAAA,EACL;AACF;;;AC5DO,SAAS,yBAAyB,QAAmC,WAAW,MAAc;AACnG,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,QAAQ,OAAO,MAAM,GAAG,EAAE,CAAC,GAAG,MAAM,GAAG,EAAE,CAAC,GAAG,KAAK;AACxD,SAAO,SAAS;AAClB;;;AF4BA,eAAsB,iBAAiB,SAAyB,CAAC,GAAyB;AACxF,QAAM,IAAI,MAAM,QAAQ;AACxB,QAAM,SAAS,OAAO,UAAU,yBAAyB,EAAE,IAAI,iBAAiB,CAAC;AACjF,SAAO;AAAA,IACL,cAAc;AAAA,MACZ,OAAO,OAAO;AAAA,MACd,QAAQ,OAAO;AAAA,MACf,SAAS,OAAO;AAAA,MAChB,cAAc,OAAO;AAAA,MACrB;AAAA,MACA,SAAS,OAAO;AAAA,MAChB,OAAO,OAAO;AAAA,MACd,OAAO,OAAO;AAAA,IAChB,CAAC;AAAA,EACH;AACF;AAMO,IAAM,iBAAoE,MAAM,gBAAgB;AAOhG,SAAS,mBAAmB,YAAqC,CAAC,GAAgB;AACvF,SAAO,kBAAkB,cAAc,SAAS,CAAC;AACnD;;;AG/DA,SAAS,SAAAA,cAAa;AAkBtB,IAAM,2BAAmD,EAAE,SAAS,UAAU;AAO9E,eAAsB,YACpB,QACA,MACA,QACA,UAA8B,CAAC,GACR;AACvB,MAAI;AACF,UAAM,SAAS,MAAM,OAAO,WAAW,MAAM,MAAM;AAEnD,QAAI,OAAO,SAAS,MAAO,QAAO,EAAE,MAAM,MAAM;AAChD,QAAI,OAAO,SAAS,cAAc,OAAO,UAAU;AACjD,aAAO,EAAE,MAAM,YAAY,aAAa,OAAO,SAAS,YAAY;AAAA,IACtE;AACA,QAAI,CAAC,OAAO,KAAM,QAAO,EAAE,MAAM,MAAM;AAEvC,UAAM,OAAO,OAAO;AACpB,UAAM,SAAS,KAAK,UAAU,CAAC;AAE/B,UAAM,UAAU,KAAK,WAAW,CAAC;AACjC,UAAM,cAA6B,QAAQ,MAAM,KAAK,KAAK,QAAQ;AACnE,QAAI,eAAe,gBAAgB,QAAQ;AACzC,YAAM,eAAe,QAAQ,mBAAmB,0BAA0B,WAAW;AACrF,UAAI,aAAa;AACf,cAAM,UAAiC;AAAA,UACrC,MAAM,GAAG,WAAW,IAAI,QAAQ,IAAI,KAAK,KAAK,EAAE;AAAA,UAChD,MAAM;AAAA,UACN;AAAA,UACA,OAAO;AAAA,UACP,QAAQ;AAAA,QACV;AACA,eAAO,MAAM,IAAI,CAAC,SAAS,GAAI,OAAO,MAAM,KAAK,CAAC,CAAE;AAAA,MACtD,OAAO;AACL,gBAAQ,KAAK,yBAAyB,WAAW;AAAA,MACnD;AAAA,IACF;AAEA,WAAO;AAAA,MACL,MAAM;AAAA,MACN,UAAU;AAAA,QACR,IAAI,KAAK;AAAA,QACT,MAAM,KAAK;AAAA,QACX,MAAM,KAAK,QAAQ;AAAA,QACnB,WAAW,KAAK,aAAa;AAAA,QAC7B;AAAA,QACA,QAAQ,KAAK;AAAA,QACb,OAAO,KAAK;AAAA,QACZ,eAAe,KAAK;AAAA,QACpB,UAAU,KAAK;AAAA,QACf,wBAAwB,OAAO,0BAA0B,CAAC;AAAA,QAC1D,aAAa,OAAO,eAAe,CAAC;AAAA,MACtC;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,WAAO;AAAA,MACL,MAAM;AAAA,MACN,SAAS,iBAAiB,QAAQ,MAAM,UAAU;AAAA,MAClD,OAAO;AAAA,IACT;AAAA,EACF;AACF;AAWO,SAAS,mBACd,QACA,UAA8B,CAAC,GAC2B;AAC1D,SAAOA,OAAM,CAAC,MAAc,WAAoB,YAAY,QAAQ,MAAM,QAAQ,OAAO,CAAC;AAC5F;;;AClFO,SAAS,kBAAkB,UAAwB,UAA+B,CAAC,GAAa;AACrG,QAAM,MAAM,SAAS,YAAY,CAAC;AAClC,QAAM,QAAQ,IAAI,SAAS,SAAS;AACpC,QAAM,aAAa,SAAS,0BAA0B,CAAC;AACvD,QAAM,UAAU,QAAQ,SAAS,QAAQ,QAAQ,EAAE;AACnD,QAAM,SAAS,QAAQ,SAAS,IAAI,QAAQ,OAAO,QAAQ,cAAc,EAAE,CAAC,KAAK;AAEjF,QAAM,WAAqB,EAAE,MAAM;AAEnC,MAAI,IAAI,YAAa,UAAS,cAAc,IAAI;AAChD,MAAI,IAAI,SAAU,UAAS,WAAW,IAAI;AAE1C,QAAM,UAAU,IAAI,SAAS,OAAO,IAAI,SAAS;AACjD,QAAM,cAAc,IAAI,iBAAiB,IAAI;AAC7C,WAAS,YAAY;AAAA,IACnB,OAAO,IAAI,WAAW;AAAA,IACtB,GAAI,cAAc,EAAE,YAAY,IAAI,CAAC;AAAA,IACrC,GAAI,IAAI,SAAS,EAAE,MAAM,IAAI,OAAO,IAAI,CAAC;AAAA,IACzC,GAAI,UAAU,EAAE,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC;AAAA,EACzC;AAEA,MAAI,IAAI,aAAa;AACnB,aAAS,UAAU;AAAA,MACjB,MAAM,IAAI;AAAA,MACV,OAAO,IAAI,WAAW;AAAA,MACtB,GAAI,cAAc,EAAE,YAAY,IAAI,CAAC;AAAA,MACrC,GAAI,UAAU,EAAE,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC;AAAA,IACzC;AAAA,EACF;AAEA,MAAI,IAAI,YAAY,UAAa,IAAI,aAAa,UAAa,IAAI,QAAQ;AACzE,aAAS,SAAS,IAAI,UAAU,EAAE,OAAO,CAAC,IAAI,SAAS,QAAQ,CAAC,IAAI,SAAS;AAAA,EAC/E;AAEA,MAAI,IAAI,WAAW;AACjB,aAAS,aAAa,EAAE,WAAW,IAAI,UAAU;AAAA,EACnD,WAAW,SAAS;AAClB,UAAM,YAAY,SAAS,YACvB,GAAG,OAAO,GAAG,MAAM,IAAI,SAAS,SAAS,KACzC,GAAG,OAAO,GAAG,MAAM;AACvB,aAAS,aAAa,EAAE,UAAU;AAAA,EACpC;AAEA,MAAI,WAAW,WAAW,SAAS,GAAG;AACpC,UAAM,YAAoC,CAAC;AAC3C,eAAW,OAAO,YAAY;AAC5B,gBAAU,IAAI,MAAM,IAAI,GAAG,OAAO,GAAG,IAAI,SAAS;AAAA,IACpD;AACA,aAAS,aAAa,EAAE,GAAG,SAAS,YAAY,UAAU;AAAA,EAC5D;AAEA,SAAO;AACT;;;ACrEA,OAAO,WAA4C;AACnD;AAAA,EACE;AAAA,EACA;AAAA,OAGK;;;ACgDP,IAAM,aAAsC;AAAA,EAC1C;AAAA,EAAW;AAAA,EAAqB;AAAA,EAAa;AAAA,EAAuB;AAAA,EAAU;AAAA,EAC9E;AAAA,EAAc;AAAA,EAAc;AAAA,EAAS;AAAA,EAAmB;AAAA,EAAU;AAAA,EAAS;AAAA,EAC3E;AAAA,EAAe;AAAA,EAAyB;AAAA,EAAW;AAAA,EACnD;AAAA,EAAW;AAAA,EAAqB;AAAA,EAAQ;AAC1C;AAGA,IAAM,gBAAgD;AAAA,EACpD,CAAC,CAAC,cAAc,YAAY,GAAG,UAAU,CAAC,WAAW,QAAQ,MAAM,QAAQ,QAAQ,SAAS,MAAM,CAAC;AAAA,EACnG,CAAC,CAAC,cAAc,UAAU,GAAG,eAAe,CAAC,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,QAAQ,MAAM,MAAM,OAAO,OAAO,KAAK,CAAC;AAAA,EACrI,CAAC,CAAC,cAAc,YAAY,GAAG,iBAAiB,CAAC,UAAU,UAAU,YAAY,MAAM,CAAC;AAAA,EACxF,CAAC,CAAC,cAAc,YAAY,GAAG,iBAAiB,CAAC,SAAS,UAAU,SAAS,CAAC;AAAA,EAC9E,CAAC,CAAC,SAAS,GAAG,aAAa,CAAC,MAAM,MAAM,MAAM,MAAM,MAAM,KAAK,CAAC;AAAA,EAChE,CAAC,CAAC,cAAc,GAAG,YAAY,CAAC,QAAQ,MAAM,MAAM,MAAM,MAAM,MAAM,CAAC;AAAA,EACvE,CAAC,CAAC,SAAS,GAAG,YAAY,CAAC,MAAM,MAAM,MAAM,IAAI,CAAC;AAAA,EAClD,CAAC,CAAC,QAAQ,GAAG,KAAK,CAAC,qBAAqB,gBAAgB,gBAAgB,cAAc,CAAC;AACzF;AAEA,IAAM,QAAQ,CAAC,QAAwB,IAAI,QAAQ,UAAU,CAAC,MAAM,IAAI,EAAE,YAAY,CAAC,EAAE;AAEzF,SAAS,UAAU,QAA6C;AAC9D,MAAI,CAAC,OAAQ,QAAO,CAAC;AACrB,QAAM,OAAiB,CAAC;AACxB,aAAW,OAAO,YAAY;AAC5B,UAAM,QAAQ,OAAO,GAAG;AACxB,QAAI,MAAO,MAAK,KAAK,WAAW,MAAM,GAAG,CAAC,KAAK,KAAK,GAAG;AAAA,EACzD;AACA,SAAO;AACT;AAEA,SAAS,KAAK,UAA+B,MAAoD;AAC/F,MAAI,UAAe;AACnB,aAAW,WAAW,MAAM;AAC1B,cAAU,UAAU,OAAO;AAC3B,QAAI,CAAC,QAAS,QAAO;AAAA,EACvB;AACA,SAAO;AACT;AAMO,SAAS,mBAAmB,UAAwD;AACzF,MAAI,CAAC,SAAU,QAAO;AAEtB,QAAM,IAAI;AACV,QAAM,YAAY,UAAU,EAAE,QAAQ,KAAK;AAC3C,QAAM,WAAW,UAAU,EAAE,QAAQ,IAAI;AACzC,QAAM,aAAuB,CAAC;AAE9B,aAAW,CAAC,MAAM,QAAQ,IAAI,KAAK,eAAe;AAChD,UAAM,QAAQ,KAAK,UAAU,IAAI;AACjC,QAAI,CAAC,MAAO;AACZ,eAAW,OAAO,MAAM;AACtB,YAAM,QAAQ,MAAM,GAAG;AACvB,UAAI,MAAO,YAAW,KAAK,GAAG,MAAM,IAAI,MAAM,GAAG,CAAC,KAAK,KAAK,GAAG;AAAA,IACjE;AAAA,EACF;AAEA,QAAM,YAAsB,CAAC;AAE7B,MAAI,WAAW,SAAS,GAAG;AACzB,cAAU,KAAK;AAAA,IAAc,WAAW,KAAK,MAAM,CAAC;AAAA,EAAK;AAAA,EAC3D;AAEA,MAAI,UAAU,SAAS,GAAG;AACxB,cAAU,KAAK;AAAA,IAAc,UAAU,KAAK,MAAM,CAAC;AAAA,EAAK;AAAA,EAC1D;AAEA,MAAI,SAAS,SAAS,GAAG;AACvB,cAAU,KAAK;AAAA;AAAA,MAAyD,SAAS,KAAK,QAAQ,CAAC;AAAA;AAAA,EAAU;AACzG,cAAU,KAAK;AAAA,IAAc,SAAS,KAAK,MAAM,CAAC;AAAA,EAAK;AAAA,EACzD;AAEA,SAAO,UAAU,KAAK,MAAM;AAC9B;;;AD1BI,SA8F0C,UA7FxC,KADF;AAvEJ,IAAM,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmBzB,SAAS,mBAAmB,MAAsB;AAChD,QAAM,YAAY,KAAK,MAAM,+BAA+B;AAC5D,SAAO,YAAY,CAAC,IAAI,UAAU,CAAC,EAAE,KAAK,IAAI;AAChD;AAGO,SAAS,wBAAwB,OAA+B;AACrE,QAAM,MAAgB,CAAC;AACvB,aAAW,QAAQ,OAAO;AACxB,QAAI,UAAU,QAAQ,KAAK,SAAS,YAAY,KAAK,iBAAiB;AACpE,UAAI,KAAK,KAAK,eAAe;AAAA,IAC/B;AACA,QAAI,UAAU,QAAQ,KAAK,SAAS,oBAAoB,MAAM,QAAQ,KAAK,OAAO,GAAG;AACnF,iBAAW,UAAU,KAAK,SAAS;AACjC,YAAI,MAAM,QAAQ,OAAO,QAAQ,EAAG,KAAI,KAAK,GAAG,wBAAwB,OAAO,QAAQ,CAAC;AAAA,MAC1F;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAOO,SAAS,gBAAgB,cAAsB,UAAwB,aAAqB,QAAwB;AACzH,MAAI,WAAW,aACZ,QAAQ,uBAAuB,SAAS,IAAI,EAC5C,QAAQ,sBAAsB,MAAM,EACpC,QAAQ,6BAA6B,EAAE;AAE1C,aAAW,SAAS,QAAQ,0BAA0B,cAAc,UAAU,WAAW,aAAa,EAAE;AACxG,aAAW,SAAS,QAAQ,0BAA0B,eAAe;AACrE,aAAW,SAAS,QAAQ,4BAA4B,EAAE;AAC1D,aAAW,SAAS,QAAQ,iBAAiB,EAAE;AAE/C,SAAO,mBAAmB,QAAQ;AACpC;AAGO,SAAS,oBAAoB,UAAgC;AAClE,QAAM,SAAiB,CAAC;AACxB,QAAM,UAAU,oBAAI,IAAI,CAAC,GAAG,OAAO,KAAK,SAAS,MAAM,GAAG,GAAG,OAAO,KAAK,SAAS,eAAe,UAAU,CAAC,CAAC,CAAC,CAAC;AAC/G,aAAW,UAAU,SAAS;AAC5B,WAAO,MAAM,IAAI,CAAC,GAAI,SAAS,eAAe,SAAS,MAAM,KAAK,CAAC,GAAI,GAAI,SAAS,OAAO,MAAM,KAAK,CAAC,CAAE;AAAA,EAC3G;AACA,SAAO;AACT;AAEA,IAAM,WAAW,CAAC,SAAiB,YACjC,oBAAC,SAAI,OAAO,EAAE,SAAS,QAAQ,YAAY,UAAU,gBAAgB,UAAU,WAAW,QAAQ,GAChG,+BAAC,SAAI,OAAO,EAAE,WAAW,SAAS,GAChC;AAAA,sBAAC,QAAG,OAAO,EAAE,UAAU,UAAU,YAAY,KAAK,cAAc,SAAS,GAAI,mBAAQ;AAAA,EACrF,oBAAC,OAAE,OAAO,EAAE,SAAS,IAAI,GAAI,mBAAQ;AAAA,GACvC,GACF;AASF,eAAsB,cAAc;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP,SAAS,MAAM;AAAA,EACf,cAAc,mBAAmB;AAAA,EACjC;AAAA,EACA;AAAA,EACA,gBAAgB;AAAA,EAChB;AACF,GAA+C;AAC7C,MAAI;AACJ,MAAI,cAAc;AAElB,MAAI,SAAS,eAAe,eAAe;AACzC,mBAAe,SAAS,cAAc;AACtC,kBAAc,SAAS,cAAc,gBAAgB;AAAA,EACvD,WAAW,SAAS,OAAO,uBAAuB;AAChD,mBAAe,SAAS,MAAM;AAC9B,kBAAc,SAAS,MAAM,wBAAwB;AAAA,EACvD,OAAO;AACL,mBAAe;AAAA,EACjB;AAEA,QAAM,mBAAmB,gBAAgB,cAAc,UAAU,aAAa,MAAM;AAEpF,QAAM,YAAuB;AAAA,IAC3B,IAAI,SAAS;AAAA,IACb,MAAM;AAAA,IACN,MAAM,SAAS;AAAA,IACf,MAAM,SAAS,QAAQ;AAAA,IACvB,aAAa,SAAS,eAAe,CAAC;AAAA,EACxC;AAEA,QAAM,oBAAuC;AAAA,IAC3C,QAAQ,SAAS;AAAA,IACjB,aAAa,OAAO,SAAS,QAAQ;AAAA,IACrC;AAAA,IACA;AAAA,EACF;AACA,QAAM,iBAAyB,CAAC;AAChC,aAAW,CAAC,QAAQ,QAAQ,KAAK,OAAO,QAAQ,oBAAoB,QAAQ,CAAC,GAAG;AAC9E,mBAAe,MAAM,IAAI,4BAA4B,UAAU,iBAAiB;AAAA,EAClF;AAEA,QAAM,cACJ,uBACC,SAAS,CAAC,OAAe,OAAO,cAAc,IAAI,MAAM,IAAI;AAC/D,QAAM,mBAAmB,OAAO,OAAO,cAAc,EAAE,QAAQ,uBAAuB;AACtF,QAAM,oBAAoB,oBAAI,IAA0B;AAExD,MAAI,iBAAiB,SAAS,GAAG;AAC/B,QAAI,CAAC,aAAa;AAChB,YAAM,IAAI,MAAM,6FAA6F;AAAA,IAC/G;AACA,UAAM,UAAU,MAAM,QAAQ;AAAA,MAC5B,CAAC,GAAG,IAAI,IAAI,gBAAgB,CAAC,EAAE,IAAI,OAAO,OAAO;AAC/C,YAAI;AACF,gBAAM,OAAO,MAAM,YAAY,EAAE;AACjC,gBAAM,OAAO,MAAM,SAAS,MAAM;AAClC,cAAI,MAAM,QAAQ,IAAI,KAAK,KAAK,SAAS,EAAG,QAAO,EAAE,IAAI,UAAU,KAAK;AAAA,QAC1E,SAAS,KAAK;AACZ,kBAAQ,MAAM,iCAAiC,EAAE,KAAK,GAAG;AAAA,QAC3D;AACA,eAAO;AAAA,MACT,CAAC;AAAA,IACH;AACA,eAAW,UAAU,SAAS;AAC5B,UAAI,OAAQ,mBAAkB,IAAI,OAAO,IAAI,OAAO,QAAQ;AAAA,IAC9D;AAAA,EACF;AAEA,QAAM,eAAuC;AAAA,IAC3C,SAAS,CAAC,YAAY;AACpB,UAAI,EAAE,UAAU,YAAY,OAAO,QAAQ,SAAS,SAAU;AAC9D,YAAM,cAAc,QAAQ,KAAK,MAAM,sBAAsB;AAC7D,UAAI,CAAC,cAAc,CAAC,EAAG;AAEvB,YAAM,YAAY,YAAY,CAAC,EAAE,YAAY;AAC7C,YAAM,iBAAiB,eAAe,SAAS,KAAK,CAAC;AACrD,UAAI,eAAe,WAAW,EAAG,QAAO,gCAAE;AAE1C,YAAM,sBAAsB,IAAI;AAAA,SAC7B,SAAS,eAAe,SAAS,SAAS,KAAK,CAAC,GAAG,IAAI,CAAC,YAAY,QAAQ,IAAI;AAAA,MACnF;AAEA,aACE,gCACG,yBAAe,IAAI,CAAC,YACnB;AAAA,QAAC;AAAA;AAAA,UAEC;AAAA,UACA;AAAA,UACA;AAAA,UACA,oBAAoB;AAAA,UACpB;AAAA,UACA;AAAA,UACA,cAAc,oBAAoB,IAAI,QAAQ,IAAI;AAAA;AAAA,QAP7C,QAAQ;AAAA,MAQf,CACD,GACH;AAAA,IAEJ;AAAA,EACF;AAEA,QAAM,mBAAmB,mBAAmB,SAAS,OAAO,QAAQ;AAEpE,MAAI;AACF,UAAM,kBAAkB,MAAM,kBAAkB,YAAY;AAE5D,WACE,iCACG;AAAA,0BAAoB,oBAAC,WAAM,yBAAyB,EAAE,QAAQ,iBAAiB,GAAG;AAAA,MAClF,eAAe,oBAAC,WAAM,yBAAyB,EAAE,QAAQ,YAAY,GAAG;AAAA,MACxE;AAAA,MACA,iBACC;AAAA,QAAC;AAAA;AAAA,UACC,OAAO;AAAA,YACL,UAAU;AAAA,YACV,QAAQ;AAAA,YACR,OAAO;AAAA,YACP,SAAS;AAAA,YACT,cAAc;AAAA,YACd,YAAY;AAAA,YACZ,OAAO;AAAA,YACP,UAAU;AAAA,YACV,YAAY;AAAA,YACZ,WAAW;AAAA,YACX,QAAQ;AAAA,UACV;AAAA,UAEC,mBAAS,UAAU,kBAAkB;AAAA;AAAA,MACxC;AAAA,OAEJ;AAAA,EAEJ,SAAS,OAAO;AACd,YAAQ,MAAM,6BAA6B,KAAK;AAChD,QAAI,YAAa,QAAO,gCAAG,sBAAY,KAAK,GAAE;AAC9C,WAAO,SAAS,kBAAkB,iBAAiB,QAAQ,MAAM,UAAU,2BAA2B;AAAA,EACxG;AACF;;;AErQA,SAAS,+BAAAC,oCAA2D;AAepE,SAAS,gBAAgB,OAAqB,aAAmC;AAC/E,QAAM,SAAuB,CAAC;AAC9B,aAAW,QAAQ,OAAO;AACxB,QAAI,UAAU,QAAQ,KAAK,SAAS,YAAY,KAAK,gBAAgB,aAAa;AAChF,aAAO,KAAK,IAAI;AAAA,IAClB,WAAW,UAAU,QAAQ,KAAK,SAAS,oBAAoB,MAAM,QAAQ,KAAK,OAAO,GAAG;AAC1F,iBAAW,UAAU,KAAK,SAAS;AACjC,eAAO,KAAK,GAAG,gBAAgB,OAAO,YAAY,CAAC,GAAG,WAAW,CAAC;AAAA,MACpE;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,IAAM,aAAa,EAAE,gBAAgB,4BAA4B;AAYjE,eAAsB,oBACpB,QACA,MACA,gBACA,UAAiC,CAAC,GACf;AACnB,QAAM,SAAS,QAAQ,UAAU,OAAO,OAAO,UAAU;AACzD,QAAM,cAAc,QAAQ,eAAe,mBAAmB;AAC9D,QAAM,cAAc,QAAQ,eAAe;AAC3C,QAAM,WAAW,QAAQ,YAAY,CAAC,WAAW,QAAQ,MAAM;AAE/D,MAAI;AACF,UAAM,OAAO,MAAM,OAAO,cAAc,MAAM,MAAM;AACpD,QAAI,MAAM,QAAQ;AAChB,YAAM,oBAAuC,EAAE,QAAQ,YAAY;AACnE,YAAM,WAAyB,CAAC;AAChC,iBAAW,UAAU,OAAO,OAAO,KAAK,MAAM,GAAG;AAC/C,YAAI,MAAM,QAAQ,MAAM,GAAG;AACzB,mBAAS,KAAK,GAAG,gBAAgBC,6BAA4B,QAAQ,iBAAiB,GAAG,WAAW,CAAC;AAAA,QACvG;AAAA,MACF;AAEA,YAAM,UAAU,SACb,IAAI,CAAC,YAAY;AAChB,cAAM,QAAiC,WAAW,UAAW,QAAQ,SAAS,CAAC,IAAK,CAAC;AACrF,cAAM,QAAQ,SAAS,IAAI,CAAC,QAAQ,MAAM,GAAG,CAAC,EAAE,KAAK,CAAC,MAAM,OAAO,MAAM,YAAY,CAAC;AACtF,eAAO,OAAO,UAAU,WAAW,QAAQ;AAAA,MAC7C,CAAC,EACA,OAAO,OAAO,EACd,KAAK,MAAM;AAEd,UAAI,QAAS,QAAO,IAAI,SAAS,SAAS,EAAE,SAAS,WAAW,CAAC;AAAA,IACnE;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,SAAO,IAAI,SAAS,gBAAgB,EAAE,SAAS,WAAW,CAAC;AAC7D;AAGO,SAAS,qBAAqB,cAAc,mBAAmB,GAAW;AAC/E,SAAO,gBAAgB,eAAe,8BAA8B;AACtE;;;ACnFA,SAAS,UAAU,mBAAmB,gBAAgB;AA6FjB,qBAAAC,WAAA,OAAAC,YAAA;AAhDrC,eAAsB,mBAAmB,QAAoD;AAC3F,QAAM,QAAQ,MAAM,QAAQ,QAAQ,CAAC;AACrC,SAAO,KAAK,SAAS,IAAI,KAAK,KAAK,GAAG,IAAI;AAC5C;AAgBO,SAAS,iBAAiB,QAA0B;AACzD,QAAM,EAAE,QAAQ,QAAQ,kBAAkB,IAAI;AAC9C,QAAM,SAAS,OAAO,SAAS,IAAI,OAAO,OAAO,QAAQ,cAAc,EAAE,CAAC,KAAK;AAC/E,QAAM,QAAwB,aAAa,EAAE,QAAQ,cAAc,OAAO,QAAQ,OAAO,EAAE,GAAG,GAAG,OAAO,MAAM,CAAC;AAC/G,QAAMC,eAAc,mBAAmB,QAAQ,OAAO,cAAc;AACpE,QAAM,eAAuC,OAAO,aAAa,cAAc,WAAW;AAE1F,iBAAe,uBAAuD;AACpE,UAAM,WAAW,MAAM,OAAO,gBAAgB,EAAE,QAAQ,OAAO,OAAO,qBAAqB,IAAK,CAAC;AACjG,YAAQ,SAAS,QAAQ,CAAC,GAAG,IAAI,CAAC,UAAU,EAAE,MAAM,OAAO,KAAK,MAAM,GAAG,IAAI,CAAC,EAAE,EAAE;AAAA,EACpF;AAEA,iBAAe,iBAAiB,EAAE,OAAO,GAAuC;AAC9E,UAAM,WAAW,MAAMA,aAAY,MAAM,mBAAmB,MAAM,GAAG,MAAM;AAC3E,QAAI,SAAS,SAAS,OAAQ,QAAO,CAAC;AACtC,WAAO,kBAAkB,SAAS,UAAU,EAAE,SAAS,OAAO,SAAS,OAAO,CAAC;AAAA,EACjF;AAEA,iBAAe,KAAK,EAAE,OAAO,GAAoB;AAC/C,UAAM,OAAO,MAAM,mBAAmB,MAAM;AAC5C,UAAM,WAAyB,MAAMA,aAAY,MAAM,MAAM;AAE7D,QAAI,SAAS,SAAS,SAAS;AAC7B,YAAM,OAAO,OAAO,kBAAkB,QAAQ,IAAI,aAAa,gBAAgB,WAAW;AAC1F,UAAI,SAAS,SAAS;AACpB,cAAM,IAAI,MAAM,mCAAmC,IAAI,MAAM,MAAM,MAAM,SAAS,OAAO,IAAI;AAAA,UAC3F,OAAO,SAAS;AAAA,QAClB,CAAC;AAAA,MACH;AACA,UAAI,OAAO,YAAa,QAAO,gBAAAD,KAAAD,WAAA,EAAG,iBAAO,YAAY,SAAS,OAAO,GAAE;AACvE,aAAO,SAAe,sBAAsB,SAAS,OAAO;AAAA,IAC9D;AACA,QAAI,SAAS,SAAS,MAAO,UAAS;AACtC,QAAI,SAAS,SAAS,WAAY,cAAa,GAAG,MAAM,GAAG,SAAS,WAAW,EAAE;AAEjF,UAAM,EAAE,SAAS,IAAI;AACrB,QAAI,SAAS,WAAW,YAAa,UAAS;AAE9C,WAAO,cAAc;AAAA,MACnB;AAAA,MACA;AAAA,MACA;AAAA,MACA,MAAM;AAAA,MACN;AAAA,MACA,aAAa,OAAO,eAAe,mBAAmB;AAAA,MACtD;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO,EAAE,sBAAsB,kBAAkB,MAAM,aAAAE,cAAa,MAAM;AAC5E;AAMO,SAAS,sBAAsB,cAAsB;AAC1D,SAAO,eAAe,UAAU,EAAE,OAAO,GAAoC;AAC3E,UAAM,QAAQ,MAAM,QAAQ,QAAQ,CAAC;AACrC,sBAAkB,GAAG,YAAY,IAAI,KAAK,KAAK,GAAG,CAAC,EAAE;AAAA,EACvD;AACF;;;AC5HA,IAAM,QAAQ,oBAAI,IAAmB;AAyB9B,SAAS,UAAU,KAAa,QAA0C;AAC/E,QAAM,MAAM,KAAK,IAAI;AACrB,QAAM,WAAW,OAAO,SAAS;AAEjC,MAAI,QAAQ,MAAM,IAAI,GAAG;AAEzB,MAAI,CAAC,SAAS,OAAO,MAAM,SAAS;AAClC,YAAQ,EAAE,OAAO,GAAG,SAAS,MAAM,SAAS;AAC5C,UAAM,IAAI,KAAK,KAAK;AACpB,WAAO,EAAE,SAAS,MAAM,WAAW,OAAO,QAAQ,GAAG,SAAS,MAAM,QAAQ;AAAA,EAC9E;AAEA,QAAM;AACN,QAAM,UAAU,MAAM,SAAS,OAAO;AAEtC,SAAO;AAAA,IACL;AAAA,IACA,WAAW,KAAK,IAAI,GAAG,OAAO,QAAQ,MAAM,KAAK;AAAA,IACjD,SAAS,MAAM;AAAA,EACjB;AACF;AAGO,SAAS,YAAY,SAA0B;AACpD,SACE,QAAQ,QAAQ,IAAI,iBAAiB,GAAG,MAAM,GAAG,EAAE,CAAC,GAAG,KAAK,KAC5D,QAAQ,QAAQ,IAAI,WAAW,KAC/B;AAEJ;;;ACjCA,cAAc;AAEd;AAAA,EACE;AAAA,EACA;AAAA,EACA,qBAAAC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;","names":["cache","filterVisibleNodesRecursive","filterVisibleNodesRecursive","Fragment","jsx","resolvePage","createBotlrClient"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@botlr/next",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Next.js helpers for Botlr: per-request clients from headers and env, on top of @botlr/sdk and @botlr/react.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"html-react-parser": "^6.1.3",
|
|
44
|
-
"@botlr/react": "^0.1.
|
|
45
|
-
"@botlr/sdk": "^0.
|
|
44
|
+
"@botlr/react": "^0.1.3",
|
|
45
|
+
"@botlr/sdk": "^0.4.0"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"next": ">=14",
|