@roottale/cms-client 0.18.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @roottale/cms-client
2
2
 
3
+ ## 0.20.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 8b46ee3: fleet well-known 컨벤션 — `createFleetInfoRoute()` (`GET /.well-known/roottale.json`)
8
+ 로 배포된 cms-client/renderer 버전 + build sha 를 노출. internal fleet API 의
9
+ `?probe=1` 이 읽어 트래픽 없이도 배포 버전 확인. cms-client 는 `CMS_CLIENT_VERSION`
10
+ 상수 export.
11
+
12
+ ## 0.19.0
13
+
14
+ ### Minor Changes
15
+
16
+ - 5fd3333: fleet 가시성 — 모든 요청에 `x-roottale-client: cms-client/<version>` 자가 보고
17
+ 헤더 추가 (build-time 주입). 플랫폼이 사이트별 클라이언트 버전을 기록해
18
+ internal `GET /v1/internal/sites` 로 노출.
19
+
3
20
  ## 0.18.0
4
21
 
5
22
  ### Minor Changes
package/dist/server.d.ts CHANGED
@@ -236,6 +236,8 @@ interface CmsPostContentPage {
236
236
  nextCursor: string | null;
237
237
  hasMore: boolean;
238
238
  }
239
+ /** 설치된 cms-client 버전 (build-time 주입) — fleet well-known 라우트 등에서 사용. */
240
+ declare const CMS_CLIENT_VERSION: string;
239
241
  interface FetchPostsOptions {
240
242
  /** `rtlk_cust_*` API key. Required. */
241
243
  apiKey: string;
@@ -272,16 +274,27 @@ interface RootTaleTheme {
272
274
  primaryForeground?: string;
273
275
  foreground?: string;
274
276
  background?: string;
277
+ secondary?: string;
278
+ secondaryForeground?: string;
279
+ accent?: string;
280
+ accentForeground?: string;
275
281
  muted?: string;
276
282
  mutedForeground?: string;
283
+ destructive?: string;
284
+ destructiveForeground?: string;
277
285
  border?: string;
286
+ input?: string;
287
+ ring?: string;
278
288
  };
279
289
  fonts?: {
280
290
  body?: string;
281
291
  display?: string;
282
292
  };
283
293
  radius?: {
294
+ sm?: string;
284
295
  md?: string;
296
+ lg?: string;
297
+ xl?: string;
285
298
  };
286
299
  }
287
300
  interface FetchThemeOptions {
@@ -398,4 +411,4 @@ declare class CmsApiError extends Error {
398
411
  declare function fetchPosts(options: FetchPostsOptions): Promise<CmsPostContentPage>;
399
412
  declare function fetchPost(options: FetchPostOptions): Promise<CmsPostContent | null>;
400
413
 
401
- export { type AnalyticsTagConfig, type AnalyticsTagProvider, type ArticleSchemaInput, CmsApiError, type CmsPostContent, type CmsPostContentPage, type CmsPostType, type CmsTaxonomyKind, type CmsTermRef, DEFAULT_BLOG_SETTINGS, type FetchAnalyticsOptions, type FetchBlogSettingsOptions, type FetchPostOptions, type FetchPostsOptions, type FetchThemeOptions, INQUIRY_ERROR_MESSAGES, type InquiryVertical, type ResolvedPostDisplay, type RootTaleBlogSettings, type RootTaleTheme, type RssFeedInput, type RssItemInput, type SiteAnalyticsTags, type SubmitInquiryFields, type SubmitInquiryOptions, type SubmitInquiryResult, articleSchema, breadcrumbSchema, faqSchema, fetchAnalyticsConfig, fetchBlogSettings, fetchPost, fetchPosts, fetchTheme, formatKoreanPhoneNumber, generateRssXml, inquiryErrorMessage, jsonLdScript, organizationSchema, parseInquiryRedirect, resolvePostDisplay, rssItemsFromPosts, submitInquiry, websiteSchema };
414
+ export { type AnalyticsTagConfig, type AnalyticsTagProvider, type ArticleSchemaInput, CMS_CLIENT_VERSION, CmsApiError, type CmsPostContent, type CmsPostContentPage, type CmsPostType, type CmsTaxonomyKind, type CmsTermRef, DEFAULT_BLOG_SETTINGS, type FetchAnalyticsOptions, type FetchBlogSettingsOptions, type FetchPostOptions, type FetchPostsOptions, type FetchThemeOptions, INQUIRY_ERROR_MESSAGES, type InquiryVertical, type ResolvedPostDisplay, type RootTaleBlogSettings, type RootTaleTheme, type RssFeedInput, type RssItemInput, type SiteAnalyticsTags, type SubmitInquiryFields, type SubmitInquiryOptions, type SubmitInquiryResult, articleSchema, breadcrumbSchema, faqSchema, fetchAnalyticsConfig, fetchBlogSettings, fetchPost, fetchPosts, fetchTheme, formatKoreanPhoneNumber, generateRssXml, inquiryErrorMessage, jsonLdScript, organizationSchema, parseInquiryRedirect, resolvePostDisplay, rssItemsFromPosts, submitInquiry, websiteSchema };
package/dist/server.js CHANGED
@@ -258,6 +258,14 @@ function assertServer() {
258
258
 
259
259
  // src/server.ts
260
260
  var DEFAULT_BASE_URL2 = "https://api.roottale.com";
261
+ var CLIENT_VERSION = true ? "0.20.0" : "dev";
262
+ var CMS_CLIENT_VERSION = CLIENT_VERSION;
263
+ function authHeaders(apiKey) {
264
+ return {
265
+ authorization: `Bearer ${apiKey}`,
266
+ "x-roottale-client": `cms-client/${CLIENT_VERSION}`
267
+ };
268
+ }
261
269
  async function fetchTheme(options) {
262
270
  assertServer2();
263
271
  const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL2).replace(/\/+$/, "");
@@ -267,7 +275,7 @@ async function fetchTheme(options) {
267
275
  const url = `${baseUrl}/v1/cms/public/theme${qs ? `?${qs}` : ""}`;
268
276
  const response = await fetch(url, {
269
277
  method: "GET",
270
- headers: { authorization: `Bearer ${options.apiKey}` },
278
+ headers: authHeaders(options.apiKey),
271
279
  signal: options.signal
272
280
  });
273
281
  if (!response.ok) {
@@ -289,7 +297,7 @@ async function fetchAnalyticsConfig(options) {
289
297
  const url = `${baseUrl}/v1/cms/public/analytics${qs ? `?${qs}` : ""}`;
290
298
  const response = await fetch(url, {
291
299
  method: "GET",
292
- headers: { authorization: `Bearer ${options.apiKey}` },
300
+ headers: authHeaders(options.apiKey),
293
301
  signal: options.signal
294
302
  });
295
303
  if (!response.ok) {
@@ -307,7 +315,7 @@ async function fetchBlogSettings(options) {
307
315
  const url = `${baseUrl}/v1/cms/public/blog-settings${qs ? `?${qs}` : ""}`;
308
316
  const response = await fetch(url, {
309
317
  method: "GET",
310
- headers: { authorization: `Bearer ${options.apiKey}` },
318
+ headers: authHeaders(options.apiKey),
311
319
  signal: options.signal
312
320
  });
313
321
  if (!response.ok) {
@@ -388,7 +396,7 @@ async function fetchPosts(options) {
388
396
  const url = `${baseUrl}/v1/cms/public/posts${qs ? `?${qs}` : ""}`;
389
397
  const response = await fetch(url, {
390
398
  method: "GET",
391
- headers: { authorization: `Bearer ${options.apiKey}` },
399
+ headers: authHeaders(options.apiKey),
392
400
  signal: options.signal
393
401
  });
394
402
  if (!response.ok) {
@@ -410,7 +418,7 @@ async function fetchPost(options) {
410
418
  const url = `${baseUrl}/v1/cms/public/posts/${encodeURIComponent(options.slugOrId)}${qs ? `?${qs}` : ""}`;
411
419
  const response = await fetch(url, {
412
420
  method: "GET",
413
- headers: { authorization: `Bearer ${options.apiKey}` },
421
+ headers: authHeaders(options.apiKey),
414
422
  signal: options.signal
415
423
  });
416
424
  if (response.status === 404) return null;
@@ -464,6 +472,7 @@ function assertServer2() {
464
472
  }
465
473
  }
466
474
  export {
475
+ CMS_CLIENT_VERSION,
467
476
  CmsApiError,
468
477
  DEFAULT_BLOG_SETTINGS,
469
478
  INQUIRY_ERROR_MESSAGES,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/seo.ts","../src/inquiry.ts","../src/server.ts"],"sourcesContent":["// 프레임워크 무관 SEO 헬퍼 — RSS 2.0 피드 + Schema.org JSON-LD 빌더.\n// 외부 사이트(Next route handler / Astro endpoint / RSC)가 fetchPosts 결과로\n// /feed.xml 응답이나 <head> JSON-LD 를 만들 때 사용. 순수 함수(네트워크 없음).\n\nimport type { CmsPostContent } from \"./server\";\n\n// ── RSS 2.0 ────────────────────────────────────────────────────────────────\n\nexport interface RssItemInput {\n title: string;\n /** 글 절대 URL. */\n link: string;\n /** 고유 식별자. 미지정 시 link 사용. */\n guid?: string;\n description?: string | null;\n /** 발행 시각. */\n pubDate?: string | Date | null;\n}\n\nexport interface RssFeedInput {\n /** 사이트/피드 제목. */\n title: string;\n /** 사이트 홈 URL. */\n link: string;\n /** 이 피드 자체의 URL (atom:self). */\n feedUrl: string;\n description?: string;\n /** 기본 \"ko-KR\". */\n language?: string;\n items: RssItemInput[];\n}\n\n/** RSS 2.0 XML 문자열 생성. */\nexport function generateRssXml(input: RssFeedInput): string {\n const lang = input.language ?? \"ko-KR\";\n const lastBuild =\n input.items.length > 0 ? toRfc822(input.items[0]!.pubDate) : \"\";\n const lines: string[] = [];\n lines.push('<?xml version=\"1.0\" encoding=\"UTF-8\"?>');\n lines.push(\n '<rss version=\"2.0\" xmlns:atom=\"http://www.w3.org/2005/Atom\" xmlns:content=\"http://purl.org/rss/1.0/modules/content/\">',\n );\n lines.push(\"<channel>\");\n lines.push(`<title>${escapeXml(input.title)}</title>`);\n lines.push(`<link>${escapeXml(input.link)}</link>`);\n lines.push(\n `<atom:link href=\"${escapeXml(input.feedUrl)}\" rel=\"self\" type=\"application/rss+xml\" />`,\n );\n lines.push(\n `<description>${escapeXml(input.description ?? input.title)}</description>`,\n );\n lines.push(`<language>${escapeXml(lang)}</language>`);\n if (lastBuild) lines.push(`<lastBuildDate>${lastBuild}</lastBuildDate>`);\n for (const item of input.items) {\n const guid = item.guid ?? item.link;\n lines.push(\"<item>\");\n lines.push(`<title>${escapeXml(item.title)}</title>`);\n lines.push(`<link>${escapeXml(item.link)}</link>`);\n lines.push(`<guid isPermaLink=\"false\">${escapeXml(guid)}</guid>`);\n if (item.description) {\n lines.push(`<description>${cdata(item.description)}</description>`);\n }\n const pub = toRfc822(item.pubDate);\n if (pub) lines.push(`<pubDate>${pub}</pubDate>`);\n lines.push(\"</item>\");\n }\n lines.push(\"</channel>\");\n lines.push(\"</rss>\");\n return lines.join(\"\\n\");\n}\n\n/** CmsPostContent[] → RSS item[]. postUrl 로 글 절대 URL 생성. */\nexport function rssItemsFromPosts(\n posts: CmsPostContent[],\n opts: { postUrl: (post: CmsPostContent) => string },\n): RssItemInput[] {\n return posts.map((p) => ({\n title: p.title,\n link: opts.postUrl(p),\n guid: opts.postUrl(p),\n description: p.excerpt,\n pubDate: p.publishedAt,\n }));\n}\n\nfunction toRfc822(value: string | Date | null | undefined): string {\n if (!value) return \"\";\n const d = value instanceof Date ? value : new Date(value);\n return Number.isNaN(d.getTime()) ? \"\" : d.toUTCString();\n}\n\n// ── Schema.org JSON-LD 빌더 ─────────────────────────────────────────────────\n\ntype JsonLd = Record<string, unknown>;\n\nexport interface ArticleSchemaInput {\n headline: string;\n url: string;\n description?: string | null;\n image?: string | null;\n datePublished?: string | Date | null;\n dateModified?: string | Date | null;\n authorName?: string | null;\n publisherName?: string;\n publisherLogo?: string;\n}\n\nexport function articleSchema(input: ArticleSchemaInput): JsonLd {\n const schema: JsonLd = {\n \"@context\": \"https://schema.org\",\n \"@type\": \"Article\",\n headline: input.headline,\n mainEntityOfPage: { \"@type\": \"WebPage\", \"@id\": input.url },\n };\n if (input.description) schema.description = input.description;\n if (input.image) schema.image = input.image;\n const pub = toIso(input.datePublished);\n if (pub) schema.datePublished = pub;\n const mod = toIso(input.dateModified);\n if (mod) schema.dateModified = mod;\n if (input.authorName)\n schema.author = { \"@type\": \"Person\", name: input.authorName };\n if (input.publisherName) {\n schema.publisher = {\n \"@type\": \"Organization\",\n name: input.publisherName,\n ...(input.publisherLogo\n ? { logo: { \"@type\": \"ImageObject\", url: input.publisherLogo } }\n : {}),\n };\n }\n return schema;\n}\n\nexport function breadcrumbSchema(\n items: { name: string; url: string }[],\n): JsonLd {\n return {\n \"@context\": \"https://schema.org\",\n \"@type\": \"BreadcrumbList\",\n itemListElement: items.map((it, i) => ({\n \"@type\": \"ListItem\",\n position: i + 1,\n name: it.name,\n item: it.url,\n })),\n };\n}\n\nexport function organizationSchema(input: {\n name: string;\n url: string;\n logo?: string;\n sameAs?: string[];\n}): JsonLd {\n return {\n \"@context\": \"https://schema.org\",\n \"@type\": \"Organization\",\n name: input.name,\n url: input.url,\n ...(input.logo ? { logo: input.logo } : {}),\n ...(input.sameAs && input.sameAs.length > 0 ? { sameAs: input.sameAs } : {}),\n };\n}\n\nexport function websiteSchema(input: {\n name: string;\n url: string;\n searchUrl?: string;\n}): JsonLd {\n return {\n \"@context\": \"https://schema.org\",\n \"@type\": \"WebSite\",\n name: input.name,\n url: input.url,\n ...(input.searchUrl\n ? {\n potentialAction: {\n \"@type\": \"SearchAction\",\n target: `${input.searchUrl}{search_term_string}`,\n \"query-input\": \"required name=search_term_string\",\n },\n }\n : {}),\n };\n}\n\nexport function faqSchema(\n qa: { question: string; answer: string }[],\n): JsonLd {\n return {\n \"@context\": \"https://schema.org\",\n \"@type\": \"FAQPage\",\n mainEntity: qa.map((x) => ({\n \"@type\": \"Question\",\n name: x.question,\n acceptedAnswer: { \"@type\": \"Answer\", text: x.answer },\n })),\n };\n}\n\n/** JSON-LD 객체 → `<script type=\"application/ld+json\">` HTML 문자열 (XSS-safe). */\nexport function jsonLdScript(schema: JsonLd | JsonLd[]): string {\n // `</script>` 주입 방지를 위해 `<` 이스케이프.\n const json = JSON.stringify(schema).replace(/</g, \"\\\\u003c\");\n return `<script type=\"application/ld+json\">${json}</script>`;\n}\n\nfunction toIso(value: string | Date | null | undefined): string | null {\n if (!value) return null;\n const d = value instanceof Date ? value : new Date(value);\n return Number.isNaN(d.getTime()) ? null : d.toISOString();\n}\n\nfunction escapeXml(value: string): string {\n return value\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\");\n}\n\nfunction cdata(value: string): string {\n return `<![CDATA[${value.replace(/]]>/g, \"]]]]><![CDATA[>\")}]]>`;\n}\n","/**\n * `@roottale/cms-client` — 리드(문의) 접수 server-side 헬퍼.\n *\n * 외부 고객사 사이트가 *블로그 조회(`fetchPosts`)와 동일한 API key 하나* 로 리드도\n * 접수하기 위한 SSR-only 클라이언트. 브라우저가 아니라 사이트 서버(Server Action /\n * Route Handler)에서 호출해야 한다 — API key 는 절대 브라우저로 노출 금지\n * (ADR-0023 §5.1 #15). 키가 tenant 를 권위 식별하므로 `tenant_slug` 불필요.\n *\n * 사용 예 (Next.js server action):\n * ```ts\n * \"use server\";\n * import { submitInquiry } from \"@roottale/cms-client/server\";\n *\n * const result = await submitInquiry({\n * apiKey: process.env.ROOTTALE_API_KEY!, // fetchPosts 와 같은 키\n * fields: {\n * vertical: \"tax\",\n * contactName: name,\n * businessName: company,\n * email,\n * phone,\n * message,\n * privacyConsent: true,\n * },\n * });\n * if (result.ok) showSuccess(); // 그 키의 tenant /crm 에 도착\n * else showError(result.message);\n * ```\n *\n * intake API 는 302 `?ok=1` / `?err=*` 로 응답 — 본 헬퍼가 파싱해 구조화 결과 반환.\n */\n\nconst DEFAULT_BASE_URL = \"https://api.roottale.com\";\n\nexport type InquiryVertical = \"consulting\" | \"medical\" | \"tax\" | \"legal\";\n\n/** intake API 의 `err` 코드 → 한국어 사용자 메시지. */\nexport const INQUIRY_ERROR_MESSAGES: Record<string, string> = {\n consent_privacy: \"개인정보 수집·이용 동의가 필요합니다.\",\n consent_overseas: \"의료 분야는 국외이전 동의가 필요합니다.\",\n invalid_vertical: \"업종 분류가 유효하지 않습니다.\",\n missing_fields: \"필수 항목이 비어 있습니다.\",\n invalid_email: \"이메일 형식이 올바르지 않습니다.\",\n invalid_api_key: \"연동 인증에 실패했습니다. API 키를 확인해주세요.\",\n internal: \"전송에 실패했습니다. 잠시 후 다시 시도해주세요.\",\n};\n\nexport interface SubmitInquiryFields {\n vertical: InquiryVertical;\n contactName: string;\n businessName: string;\n email: string;\n phone: string;\n /** 자유 문의 내용 (선택). */\n message?: string;\n /** 상담 분야 라벨 등 부가 메타 (선택). */\n consultationField?: string;\n /** 현재 사이트 URL (선택). */\n currentSiteUrl?: string;\n /** 개인정보 수집·이용 동의 (필수 true). */\n privacyConsent: boolean;\n /** 의료 vertical 의 국외이전 동의 (medical 일 때 필수). */\n overseasTransferConsent?: boolean;\n /**\n * \"patient\"(고객사 방문자 문의, 기본) | \"sales\"(RootTale 자체 B2B 영업 리드).\n * sales 는 medical 국외이전 동의 게이트 면제.\n */\n leadKind?: \"patient\" | \"sales\";\n /**\n * 표준 필드 외에 폼이 받은 임의 항목(key→value)을 있는 그대로 전달. intake 가\n * 암호화 보관하고 CRM 상세에서 노출한다. 예약 키(표준 필드)·`_` 접두 키는 무시.\n * 주의: 개인정보가 담길 수 있으므로 폼의 동의 고지에 수집 항목을 반영할 것.\n */\n extras?: Record<string, string>;\n}\n\n/** submitInquiry 가 전용으로 매핑하는 키 — extras 에서 충돌 시 무시. */\nconst RESERVED_INQUIRY_KEYS = new Set([\n \"vertical\",\n \"contact_name\",\n \"business_name\",\n \"email\",\n \"phone\",\n \"message\",\n \"consultation_field\",\n \"current_site_url\",\n \"privacy_consent\",\n \"overseas_transfer_consent\",\n \"lead_kind\",\n]);\n\nexport interface SubmitInquiryOptions {\n /** `rtlk_cust_*` API key — `fetchPosts` 와 동일한 키. Required. */\n apiKey: string;\n /** API base URL 오버라이드. 기본 `https://api.roottale.com`. */\n baseUrl?: string;\n fields: SubmitInquiryFields;\n signal?: AbortSignal;\n}\n\nexport type SubmitInquiryResult =\n | { ok: true }\n | { ok: false; code: string | null; message: string };\n\n/** `err` 코드를 사용자 메시지로 (미지의 코드는 generic). */\nexport function inquiryErrorMessage(code: string | null | undefined): string {\n return (code && INQUIRY_ERROR_MESSAGES[code]) || INQUIRY_ERROR_MESSAGES.internal;\n}\n\n/**\n * intake API 의 302 Location (`?ok=1` / `?err=*`) 을 구조화 결과로 파싱.\n * 외부 사이트가 ERROR_MESSAGES·파싱을 각자 재구현하지 않도록 공유.\n */\nexport function parseInquiryRedirect(\n location: string | null,\n base: string = DEFAULT_BASE_URL,\n): SubmitInquiryResult {\n if (!location) {\n return { ok: false, code: null, message: inquiryErrorMessage(null) };\n }\n let url: URL;\n try {\n url = new URL(location, base);\n } catch {\n return { ok: false, code: null, message: inquiryErrorMessage(null) };\n }\n if (url.searchParams.get(\"ok\") === \"1\") return { ok: true };\n const code = url.searchParams.get(\"err\");\n return { ok: false, code, message: inquiryErrorMessage(code) };\n}\n\nexport function formatKoreanPhoneNumber(input: string): string {\n let digits = input.replace(/\\D/g, \"\");\n if (digits.startsWith(\"82\") && digits.length >= 11) {\n digits = `0${digits.slice(2)}`;\n }\n if (digits.length === 11) {\n return `${digits.slice(0, 3)}-${digits.slice(3, 7)}-${digits.slice(7)}`;\n }\n if (digits.length === 10) {\n if (digits.startsWith(\"02\")) {\n return `${digits.slice(0, 2)}-${digits.slice(2, 6)}-${digits.slice(6)}`;\n }\n return `${digits.slice(0, 3)}-${digits.slice(3, 6)}-${digits.slice(6)}`;\n }\n if (digits.length === 8) {\n return `${digits.slice(0, 4)}-${digits.slice(4)}`;\n }\n return input.trim();\n}\n\n/**\n * 리드를 RootTale intake API 로 제출 (server-to-server, Bearer 인증).\n * 키의 tenant 로 라우팅되어 admin(mysite.roottale.com) /crm 에서 관리된다.\n */\nexport async function submitInquiry(\n options: SubmitInquiryOptions,\n): Promise<SubmitInquiryResult> {\n assertServer();\n const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n const f = options.fields;\n\n const body = new FormData();\n body.set(\"vertical\", f.vertical);\n body.set(\"contact_name\", f.contactName);\n body.set(\"business_name\", f.businessName);\n body.set(\"email\", f.email);\n body.set(\"phone\", formatKoreanPhoneNumber(f.phone));\n if (f.message) body.set(\"message\", f.message);\n if (f.consultationField) body.set(\"consultation_field\", f.consultationField);\n if (f.currentSiteUrl) body.set(\"current_site_url\", f.currentSiteUrl);\n // 사용자가 명시 동의한 경우에만 전달 (자동 부여 금지).\n if (f.privacyConsent) body.set(\"privacy_consent\", \"on\");\n if (f.overseasTransferConsent) body.set(\"overseas_transfer_consent\", \"on\");\n if (f.leadKind) body.set(\"lead_kind\", f.leadKind);\n // 표준 외 임의 항목 — 예약 키/`_`접두/빈 값/중복은 건너뛰고 원본 그대로 전달.\n if (f.extras) {\n for (const [rawKey, rawValue] of Object.entries(f.extras)) {\n const key = rawKey.trim();\n const value = String(rawValue ?? \"\").trim();\n if (!key || !value) continue;\n if (key.startsWith(\"_\") || RESERVED_INQUIRY_KEYS.has(key)) continue;\n if (body.has(key)) continue;\n body.set(key, value);\n }\n }\n\n let response: Response;\n try {\n response = await fetch(`${baseUrl}/v1/public/inquiries`, {\n method: \"POST\",\n headers: { authorization: `Bearer ${options.apiKey}` },\n body,\n // intake 는 302 로 결과 신호 — 자동 추적 금지하고 Location 직접 읽음.\n redirect: \"manual\",\n signal: options.signal,\n });\n } catch {\n return { ok: false, code: \"internal\", message: inquiryErrorMessage(\"internal\") };\n }\n\n // 잘못된/만료된 키 → 라우트가 401.\n if (response.status === 401) {\n return {\n ok: false,\n code: \"invalid_api_key\",\n message: inquiryErrorMessage(\"invalid_api_key\"),\n };\n }\n\n const location = response.headers.get(\"location\");\n if (location) return parseInquiryRedirect(location, baseUrl);\n\n // redirect 없이 성공 응답이면 ok 로 간주, 그 외엔 generic 실패.\n if (response.ok) return { ok: true };\n return { ok: false, code: \"internal\", message: inquiryErrorMessage(\"internal\") };\n}\n\nfunction assertServer(): void {\n if (typeof window !== \"undefined\") {\n throw new Error(\n \"@roottale/cms-client submitInquiry must not run in the browser. \" +\n \"Call it from a Server Action / Route Handler. \" +\n \"Shipping the API key to the browser breaks the security model (ADR-0023 §5.1 #15).\",\n );\n }\n}\n","/**\n * `@roottale/cms-client/server` — RootTale CMS Public API server-side fetch client.\n *\n * 외부 고객사 사이트가 RootTale CMS 콘텐츠를 fetch 하기 위한 SSR-only client.\n * Bearer (`rtlk_cust_*`) 인증, 브라우저 직접 호출 금지 (ADR-0023 §5.1 #15).\n *\n * 사용 예 (Next.js server component):\n * ```ts\n * import { fetchPosts } from \"@roottale/cms-client/server\";\n *\n * const posts = await fetchPosts({\n * apiKey: process.env.ROOTTALE_API_KEY!,\n * limit: 10,\n * });\n * ```\n *\n * 데이터 모델 = ADR-0034 `posts` (WP 7.0-aligned, type=post|page).\n * Phase 1 scope: list + get. WP import/export, draft preview = 별도 PR.\n */\n\nexport type CmsPostType = \"post\" | \"page\";\n\nexport type CmsTaxonomyKind = \"category\" | \"tag\" | \"custom\";\n\nexport interface CmsTermRef {\n id: string;\n taxonomy: CmsTaxonomyKind;\n slug: string;\n name: string;\n}\n\nexport interface CmsPostContent {\n /** UUIDv7. */\n id: string;\n /** Owning tenant id. */\n tenantId: string;\n /** Owning site id (ADR-0034 P4 dual-key). */\n siteId: string;\n /** WP post type. */\n type: CmsPostType;\n /** Lowercase kebab-case slug (1-120 chars). */\n slug: string;\n title: string;\n excerpt: string | null;\n /** Media id (resolved by renderer / consumer to URL). */\n featuredMediaId: string | null;\n /**\n * featured 이미지 delivery URL (CF Images variant, md 우선). 서버가 `featured_media_id`\n * → `media.variants_json` 를 해석해 제공. 대표 이미지 미설정 시 null.\n */\n featuredImageUrl: string | null;\n authorId: string | null;\n /** Public display name for the post author. Email is not exposed. */\n authorName: string | null;\n /** Public profile image URL for the post author. */\n authorImageUrl: string | null;\n /** Public biography text for the post author. */\n authorBio: string | null;\n /** Tiptap / Gutenberg-compatible Block JSON. Sanitization is renderer's responsibility. */\n bodyJson: Record<string, unknown>;\n /** Public API only exposes `published`. */\n status: \"published\";\n /** Per-post meta (SEO overrides, canonical, og, ...). */\n metaJson: Record<string, unknown>;\n /**\n * ADR-0041 — 포맷된 커스텀 필드(ACF) 값. 서버가 필드 정의(코드 ⊕ DB 그룹)로\n * `meta_json.acf` 를 포맷 — image/file/gallery 는 media variant URL 객체.\n * 해석된 그룹이 없거나 구 서버면 null.\n */\n fields: Record<string, unknown> | null;\n /**\n * Attached taxonomy terms (categories + tags + custom). Server 가 category →\n * tag → custom 순서로 정렬해 보내며, 각 taxonomy 내부는 name asc. attach 가 0 개면\n * 빈 배열. category 만 추출하려면 `terms.filter(t => t.taxonomy === \"category\")`.\n */\n terms: CmsTermRef[];\n /** ISO 8601. */\n publishedAt: string;\n /** ISO 8601. */\n createdAt: string;\n /** ISO 8601. */\n updatedAt: string;\n}\n\nexport interface CmsPostContentPage {\n items: CmsPostContent[];\n nextCursor: string | null;\n hasMore: boolean;\n}\n\nconst DEFAULT_BASE_URL = \"https://api.roottale.com\";\n\nexport interface FetchPostsOptions {\n /** `rtlk_cust_*` API key. Required. */\n apiKey: string;\n /** Override the API base URL. Defaults to `https://api.roottale.com`. */\n baseUrl?: string;\n /** Page size, 1-100, default 20. */\n limit?: number;\n /** Opaque pagination cursor returned from previous call. */\n cursor?: string;\n /** Filter by post type (`post` or `page`). Default = both. */\n type?: CmsPostType;\n /** Override the auto-resolved site id (Phase 1 = 1 site/tenant, usually omitted). */\n siteId?: string;\n /** Optional AbortSignal. */\n signal?: AbortSignal;\n}\n\nexport interface FetchPostOptions {\n apiKey: string;\n baseUrl?: string;\n /** Slug (kebab-case) or UUIDv7 id. */\n slugOrId: string;\n /** Override the auto-resolved site id. */\n siteId?: string;\n signal?: AbortSignal;\n}\n\n/**\n * 외부 사이트가 cms-renderer 에 주입할 RootTale 디자인 토큰.\n * admin /design (mysite.roottale.com) 에서 저장한 값과 동일한 shape.\n * 미설정 필드는 응답에서 누락 → renderer 의 CSS fallback 이 사용된다.\n */\nexport interface RootTaleTheme {\n colors?: {\n primary?: string;\n primaryForeground?: string;\n foreground?: string;\n background?: string;\n muted?: string;\n mutedForeground?: string;\n border?: string;\n };\n fonts?: {\n body?: string;\n display?: string;\n };\n radius?: {\n md?: string;\n };\n}\n\nexport interface FetchThemeOptions {\n apiKey: string;\n baseUrl?: string;\n siteId?: string;\n signal?: AbortSignal;\n}\n\n/**\n * tenant 의 RootTale 디자인 토큰을 가져온다. admin /design 의 마지막 저장값\n * (없으면 빈 객체) — renderer 가 이 값을 CSS 변수로 주입한다.\n *\n * 자동 fetch (renderer 가 theme prop 없을 때) 와 명시 호출 모두 지원.\n * 네트워크/서버 에러는 throw — 호출자가 fallback 처리.\n */\nexport async function fetchTheme(options: FetchThemeOptions): Promise<RootTaleTheme> {\n assertServer();\n const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n const params = new URLSearchParams();\n if (options.siteId) params.set(\"site_id\", options.siteId);\n const qs = params.toString();\n const url = `${baseUrl}/v1/cms/public/theme${qs ? `?${qs}` : \"\"}`;\n\n const response = await fetch(url, {\n method: \"GET\",\n headers: { authorization: `Bearer ${options.apiKey}` },\n signal: options.signal,\n });\n\n if (!response.ok) {\n throw await toApiError(response);\n }\n\n const json = (await response.json()) as {\n colors?: RootTaleTheme[\"colors\"];\n fonts?: RootTaleTheme[\"fonts\"];\n radius?: RootTaleTheme[\"radius\"];\n };\n const theme: RootTaleTheme = {};\n if (json.colors) theme.colors = json.colors;\n if (json.fonts) theme.fonts = json.fonts;\n if (json.radius) theme.radius = json.radius;\n return theme;\n}\n\nexport type AnalyticsTagProvider = \"ga4\" | \"clarity\" | \"meta_pixel\" | \"naver\";\n\n/** analytics-runtime `TagConfig` 와 구조 호환 — `buildManifest` 입력. */\nexport interface AnalyticsTagConfig {\n provider: AnalyticsTagProvider;\n id: string;\n enabled: boolean;\n}\n\nexport interface SiteAnalyticsTags {\n siteId: string;\n tags: AnalyticsTagConfig[];\n}\n\nexport interface FetchAnalyticsOptions {\n apiKey: string;\n baseUrl?: string;\n siteId?: string;\n signal?: AbortSignal;\n}\n\n/**\n * ADR-0038 plane ① — 외부 사이트가 `<head>` 에 주입할 런타임 태그(GA4 measurement\n * id / Clarity project id 등, 비밀 아님). RootTale 관리용 default + 테넌트 고객용\n * 설정을 머지한 결과를 반환한다. analytics-runtime `buildManifest` 입력으로 쓰고,\n * 동의는 항상 fail-closed(런타임이 처리)이므로 키 노출 위험 없음(public id).\n */\nexport async function fetchAnalyticsConfig(\n options: FetchAnalyticsOptions,\n): Promise<SiteAnalyticsTags> {\n assertServer();\n const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n const params = new URLSearchParams();\n if (options.siteId) params.set(\"site_id\", options.siteId);\n const qs = params.toString();\n const url = `${baseUrl}/v1/cms/public/analytics${qs ? `?${qs}` : \"\"}`;\n\n const response = await fetch(url, {\n method: \"GET\",\n headers: { authorization: `Bearer ${options.apiKey}` },\n signal: options.signal,\n });\n\n if (!response.ok) {\n throw await toApiError(response);\n }\n\n const json = (await response.json()) as {\n site_id: string;\n tags?: AnalyticsTagConfig[];\n };\n return { siteId: json.site_id, tags: json.tags ?? [] };\n}\n\n/**\n * 블로그 표시 설정 — admin /design 의 \"블로그 표시\" 에서 저장한 사이트 기본값.\n * renderer 가 prop 미지정 시 이 값을 기본으로 적용 (글별 override 는 post 의\n * metaJson.display 가 우선).\n */\nexport interface RootTaleBlogSettings {\n showTableOfContents: boolean;\n showAuthor: boolean;\n showDate: boolean;\n showAuthorCard: boolean;\n /** 목차 상단 제목. null = renderer 기본(\"목차\"). */\n tocTitle: string | null;\n /** 작성자 카드 상단 라벨. null = renderer 기본(\"작성자\"). */\n authorCardEyebrow: string | null;\n /** 작성자 카드 설명. null = 설명 없음. */\n authorCardDescription: string | null;\n /** 작성자 프로필 이름. null = 글 작성자 이름 사용. */\n authorProfileName: string | null;\n /** 작성자 프로필 bio. null = 설명 없음. */\n authorProfileBio: string | null;\n /** 작성자 프로필 이미지 URL. null = 이니셜 아바타 사용. */\n authorProfileImageUrl: string | null;\n /** 작성자 프로필 이미지 모서리. */\n authorProfileImageRadius: \"circle\" | \"rounded\" | \"square\";\n /** 작성자 프로필 이미지 object-position x (%). */\n authorProfileImagePositionX: number;\n /** 작성자 프로필 이미지 object-position y (%). */\n authorProfileImagePositionY: number;\n}\n\nexport interface FetchBlogSettingsOptions {\n apiKey: string;\n baseUrl?: string;\n siteId?: string;\n signal?: AbortSignal;\n}\n\n/**\n * tenant 의 블로그 표시 기본값을 가져온다. 네트워크/서버 에러는 throw —\n * 호출자(renderer)가 안전 기본값으로 fallback.\n */\nexport async function fetchBlogSettings(\n options: FetchBlogSettingsOptions,\n): Promise<RootTaleBlogSettings> {\n assertServer();\n const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n const params = new URLSearchParams();\n if (options.siteId) params.set(\"site_id\", options.siteId);\n const qs = params.toString();\n const url = `${baseUrl}/v1/cms/public/blog-settings${qs ? `?${qs}` : \"\"}`;\n\n const response = await fetch(url, {\n method: \"GET\",\n headers: { authorization: `Bearer ${options.apiKey}` },\n signal: options.signal,\n });\n\n if (!response.ok) {\n throw await toApiError(response);\n }\n\n const json = (await response.json()) as {\n show_table_of_contents?: boolean;\n show_author?: boolean;\n show_date?: boolean;\n show_author_card?: boolean;\n toc_title?: string | null;\n author_card_eyebrow?: string | null;\n author_card_description?: string | null;\n author_profile_name?: string | null;\n author_profile_bio?: string | null;\n author_profile_image_url?: string | null;\n author_profile_image_radius?: string | null;\n author_profile_image_position_x?: number;\n author_profile_image_position_y?: number;\n };\n const radius =\n json.author_profile_image_radius === \"rounded\" ||\n json.author_profile_image_radius === \"square\"\n ? json.author_profile_image_radius\n : \"circle\";\n const percent = (x: unknown): number =>\n typeof x === \"number\" && Number.isFinite(x)\n ? Math.max(0, Math.min(100, Math.round(x)))\n : 50;\n return {\n showTableOfContents: json.show_table_of_contents === true,\n showAuthor: json.show_author !== false,\n showDate: json.show_date !== false,\n showAuthorCard: json.show_author_card !== false,\n tocTitle:\n typeof json.toc_title === \"string\" && json.toc_title.length > 0\n ? json.toc_title\n : null,\n authorCardEyebrow:\n typeof json.author_card_eyebrow === \"string\" &&\n json.author_card_eyebrow.length > 0\n ? json.author_card_eyebrow\n : null,\n authorCardDescription:\n typeof json.author_card_description === \"string\" &&\n json.author_card_description.length > 0\n ? json.author_card_description\n : null,\n authorProfileName:\n typeof json.author_profile_name === \"string\" &&\n json.author_profile_name.length > 0\n ? json.author_profile_name\n : null,\n authorProfileBio:\n typeof json.author_profile_bio === \"string\" &&\n json.author_profile_bio.length > 0\n ? json.author_profile_bio\n : null,\n authorProfileImageUrl:\n typeof json.author_profile_image_url === \"string\" &&\n json.author_profile_image_url.length > 0\n ? json.author_profile_image_url\n : null,\n authorProfileImageRadius: radius,\n authorProfileImagePositionX: percent(json.author_profile_image_position_x),\n authorProfileImagePositionY: percent(json.author_profile_image_position_y),\n };\n}\n\n/** fetch 실패/미설정 시 사용할 안전 기본값 (기존 렌더러 동작). */\nexport const DEFAULT_BLOG_SETTINGS: RootTaleBlogSettings = {\n showTableOfContents: false,\n showAuthor: true,\n showDate: true,\n showAuthorCard: true,\n tocTitle: null,\n authorCardEyebrow: null,\n authorCardDescription: null,\n authorProfileName: null,\n authorProfileBio: null,\n authorProfileImageUrl: null,\n authorProfileImageRadius: \"circle\",\n authorProfileImagePositionX: 50,\n authorProfileImagePositionY: 50,\n};\n\nexport interface ResolvedPostDisplay {\n showTableOfContents: boolean;\n showAuthor: boolean;\n showDate: boolean;\n showAuthorCard: boolean;\n tocTitle: string;\n authorCardEyebrow: string;\n authorCardDescription: string | null;\n authorProfileName: string | null;\n authorProfileBio: string | null;\n authorProfileImageUrl: string | null;\n authorProfileImageRadius: \"circle\" | \"rounded\" | \"square\";\n authorProfileImagePositionX: number;\n authorProfileImagePositionY: number;\n}\n\n/**\n * 글 1건의 실제 표시 여부 결정. 우선순위:\n * 명시 prop > 글별 override(post.metaJson.display) > 사이트 기본값(settings).\n * tocTitle: 명시 prop > settings.tocTitle > \"목차\".\n */\nexport function resolvePostDisplay(\n post: { metaJson?: Record<string, unknown> | null },\n settings: RootTaleBlogSettings,\n explicit?: { showTableOfContents?: boolean; tableOfContentsTitle?: string },\n): ResolvedPostDisplay {\n const display =\n post.metaJson && typeof post.metaJson === \"object\"\n ? ((post.metaJson as Record<string, unknown>).display as\n | Record<string, unknown>\n | undefined)\n : undefined;\n const ov = (key: string): boolean | undefined =>\n display && typeof display[key] === \"boolean\"\n ? (display[key] as boolean)\n : undefined;\n return {\n showTableOfContents:\n explicit?.showTableOfContents ?? ov(\"toc\") ?? settings.showTableOfContents,\n showAuthor: ov(\"author\") ?? settings.showAuthor,\n showDate: ov(\"date\") ?? settings.showDate,\n showAuthorCard: settings.showAuthorCard,\n tocTitle: explicit?.tableOfContentsTitle ?? settings.tocTitle ?? \"목차\",\n authorCardEyebrow: settings.authorCardEyebrow ?? \"작성자\",\n authorCardDescription: settings.authorCardDescription,\n authorProfileName: settings.authorProfileName,\n authorProfileBio: settings.authorProfileBio,\n authorProfileImageUrl: settings.authorProfileImageUrl,\n authorProfileImageRadius: settings.authorProfileImageRadius,\n authorProfileImagePositionX: settings.authorProfileImagePositionX,\n authorProfileImagePositionY: settings.authorProfileImagePositionY,\n };\n}\n\nexport class CmsApiError extends Error {\n readonly status: number;\n readonly code: string;\n constructor(message: string, status: number, code: string) {\n super(message);\n this.name = \"CmsApiError\";\n this.status = status;\n this.code = code;\n }\n}\n\nexport async function fetchPosts(options: FetchPostsOptions): Promise<CmsPostContentPage> {\n assertServer();\n const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n const params = new URLSearchParams();\n if (options.limit !== undefined) params.set(\"limit\", String(options.limit));\n if (options.cursor) params.set(\"cursor\", options.cursor);\n if (options.type) params.set(\"type\", options.type);\n if (options.siteId) params.set(\"site_id\", options.siteId);\n const qs = params.toString();\n const url = `${baseUrl}/v1/cms/public/posts${qs ? `?${qs}` : \"\"}`;\n\n const response = await fetch(url, {\n method: \"GET\",\n headers: { authorization: `Bearer ${options.apiKey}` },\n signal: options.signal,\n });\n\n if (!response.ok) {\n throw await toApiError(response);\n }\n\n const json = (await response.json()) as {\n items: CmsPostWire[];\n has_more: boolean;\n next_cursor: string | null;\n };\n return {\n items: json.items.map(fromWire),\n hasMore: json.has_more,\n nextCursor: json.next_cursor,\n };\n}\n\nexport async function fetchPost(options: FetchPostOptions): Promise<CmsPostContent | null> {\n assertServer();\n const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n const params = new URLSearchParams();\n if (options.siteId) params.set(\"site_id\", options.siteId);\n const qs = params.toString();\n const url = `${baseUrl}/v1/cms/public/posts/${encodeURIComponent(options.slugOrId)}${qs ? `?${qs}` : \"\"}`;\n\n const response = await fetch(url, {\n method: \"GET\",\n headers: { authorization: `Bearer ${options.apiKey}` },\n signal: options.signal,\n });\n\n if (response.status === 404) return null;\n if (!response.ok) {\n throw await toApiError(response);\n }\n\n const json = (await response.json()) as CmsPostWire;\n return fromWire(json);\n}\n\ntype CmsPostWire = {\n id: string;\n tenant_id: string;\n site_id: string;\n type: CmsPostType;\n slug: string;\n title: string;\n excerpt: string | null;\n featured_media_id: string | null;\n /** featured 이미지 delivery URL. 구 서버는 미포함 — backward-compat optional. */\n featured_media_url?: string | null;\n author_id: string | null;\n author_name?: string | null;\n author_image_url?: string | null;\n author_bio?: string | null;\n body_json: Record<string, unknown>;\n status: \"published\";\n meta_json: Record<string, unknown>;\n /** ADR-0041 포맷 필드. 구 서버는 미포함 — backward-compat optional. */\n fields?: Record<string, unknown> | null;\n /** 0.2.0 이전 서버는 미포함 — backward-compat 차원에서 optional. */\n terms?: CmsTermRef[];\n published_at: string;\n created_at: string;\n updated_at: string;\n};\n\nfunction fromWire(wire: CmsPostWire): CmsPostContent {\n return {\n id: wire.id,\n tenantId: wire.tenant_id,\n siteId: wire.site_id,\n type: wire.type,\n slug: wire.slug,\n title: wire.title,\n excerpt: wire.excerpt,\n featuredMediaId: wire.featured_media_id,\n featuredImageUrl: wire.featured_media_url ?? null,\n authorId: wire.author_id,\n authorName: wire.author_name ?? null,\n authorImageUrl: wire.author_image_url ?? null,\n authorBio: wire.author_bio ?? null,\n bodyJson: wire.body_json,\n status: wire.status,\n metaJson: wire.meta_json,\n fields: wire.fields ?? null,\n terms: wire.terms ?? [],\n publishedAt: wire.published_at,\n createdAt: wire.created_at,\n updatedAt: wire.updated_at,\n };\n}\n\nasync function toApiError(response: Response): Promise<CmsApiError> {\n let code = \"request_failed\";\n let message = `RootTale CMS API error: ${response.status}`;\n try {\n const body = (await response.json()) as { code?: string; message?: string };\n if (body.code) code = body.code;\n if (body.message) message = body.message;\n } catch {\n /* ignore body parse */\n }\n return new CmsApiError(message, response.status, code);\n}\n\nfunction assertServer(): void {\n if (typeof window !== \"undefined\") {\n throw new Error(\n \"@roottale/cms-client/server must not run in the browser. \" +\n \"Move this call to a Server Component, getServerSideProps, or a Route Handler. \" +\n \"Shipping API keys to the browser breaks the security model (ADR-0023 §5.1 #15).\",\n );\n }\n}\n\n// 프레임워크 무관 SEO 헬퍼 (RSS 2.0 + Schema.org JSON-LD) 재노출.\nexport * from \"./seo\";\n\n// 리드 접수 — 블로그 조회와 동일한 API key 하나로 server-side 제출 (submitInquiry).\nexport * from \"./inquiry\";\n"],"mappings":"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","names":["DEFAULT_BASE_URL","assertServer"]}
1
+ {"version":3,"sources":["../src/seo.ts","../src/inquiry.ts","../src/server.ts"],"sourcesContent":["// 프레임워크 무관 SEO 헬퍼 — RSS 2.0 피드 + Schema.org JSON-LD 빌더.\n// 외부 사이트(Next route handler / Astro endpoint / RSC)가 fetchPosts 결과로\n// /feed.xml 응답이나 <head> JSON-LD 를 만들 때 사용. 순수 함수(네트워크 없음).\n\nimport type { CmsPostContent } from \"./server\";\n\n// ── RSS 2.0 ────────────────────────────────────────────────────────────────\n\nexport interface RssItemInput {\n title: string;\n /** 글 절대 URL. */\n link: string;\n /** 고유 식별자. 미지정 시 link 사용. */\n guid?: string;\n description?: string | null;\n /** 발행 시각. */\n pubDate?: string | Date | null;\n}\n\nexport interface RssFeedInput {\n /** 사이트/피드 제목. */\n title: string;\n /** 사이트 홈 URL. */\n link: string;\n /** 이 피드 자체의 URL (atom:self). */\n feedUrl: string;\n description?: string;\n /** 기본 \"ko-KR\". */\n language?: string;\n items: RssItemInput[];\n}\n\n/** RSS 2.0 XML 문자열 생성. */\nexport function generateRssXml(input: RssFeedInput): string {\n const lang = input.language ?? \"ko-KR\";\n const lastBuild =\n input.items.length > 0 ? toRfc822(input.items[0]!.pubDate) : \"\";\n const lines: string[] = [];\n lines.push('<?xml version=\"1.0\" encoding=\"UTF-8\"?>');\n lines.push(\n '<rss version=\"2.0\" xmlns:atom=\"http://www.w3.org/2005/Atom\" xmlns:content=\"http://purl.org/rss/1.0/modules/content/\">',\n );\n lines.push(\"<channel>\");\n lines.push(`<title>${escapeXml(input.title)}</title>`);\n lines.push(`<link>${escapeXml(input.link)}</link>`);\n lines.push(\n `<atom:link href=\"${escapeXml(input.feedUrl)}\" rel=\"self\" type=\"application/rss+xml\" />`,\n );\n lines.push(\n `<description>${escapeXml(input.description ?? input.title)}</description>`,\n );\n lines.push(`<language>${escapeXml(lang)}</language>`);\n if (lastBuild) lines.push(`<lastBuildDate>${lastBuild}</lastBuildDate>`);\n for (const item of input.items) {\n const guid = item.guid ?? item.link;\n lines.push(\"<item>\");\n lines.push(`<title>${escapeXml(item.title)}</title>`);\n lines.push(`<link>${escapeXml(item.link)}</link>`);\n lines.push(`<guid isPermaLink=\"false\">${escapeXml(guid)}</guid>`);\n if (item.description) {\n lines.push(`<description>${cdata(item.description)}</description>`);\n }\n const pub = toRfc822(item.pubDate);\n if (pub) lines.push(`<pubDate>${pub}</pubDate>`);\n lines.push(\"</item>\");\n }\n lines.push(\"</channel>\");\n lines.push(\"</rss>\");\n return lines.join(\"\\n\");\n}\n\n/** CmsPostContent[] → RSS item[]. postUrl 로 글 절대 URL 생성. */\nexport function rssItemsFromPosts(\n posts: CmsPostContent[],\n opts: { postUrl: (post: CmsPostContent) => string },\n): RssItemInput[] {\n return posts.map((p) => ({\n title: p.title,\n link: opts.postUrl(p),\n guid: opts.postUrl(p),\n description: p.excerpt,\n pubDate: p.publishedAt,\n }));\n}\n\nfunction toRfc822(value: string | Date | null | undefined): string {\n if (!value) return \"\";\n const d = value instanceof Date ? value : new Date(value);\n return Number.isNaN(d.getTime()) ? \"\" : d.toUTCString();\n}\n\n// ── Schema.org JSON-LD 빌더 ─────────────────────────────────────────────────\n\ntype JsonLd = Record<string, unknown>;\n\nexport interface ArticleSchemaInput {\n headline: string;\n url: string;\n description?: string | null;\n image?: string | null;\n datePublished?: string | Date | null;\n dateModified?: string | Date | null;\n authorName?: string | null;\n publisherName?: string;\n publisherLogo?: string;\n}\n\nexport function articleSchema(input: ArticleSchemaInput): JsonLd {\n const schema: JsonLd = {\n \"@context\": \"https://schema.org\",\n \"@type\": \"Article\",\n headline: input.headline,\n mainEntityOfPage: { \"@type\": \"WebPage\", \"@id\": input.url },\n };\n if (input.description) schema.description = input.description;\n if (input.image) schema.image = input.image;\n const pub = toIso(input.datePublished);\n if (pub) schema.datePublished = pub;\n const mod = toIso(input.dateModified);\n if (mod) schema.dateModified = mod;\n if (input.authorName)\n schema.author = { \"@type\": \"Person\", name: input.authorName };\n if (input.publisherName) {\n schema.publisher = {\n \"@type\": \"Organization\",\n name: input.publisherName,\n ...(input.publisherLogo\n ? { logo: { \"@type\": \"ImageObject\", url: input.publisherLogo } }\n : {}),\n };\n }\n return schema;\n}\n\nexport function breadcrumbSchema(\n items: { name: string; url: string }[],\n): JsonLd {\n return {\n \"@context\": \"https://schema.org\",\n \"@type\": \"BreadcrumbList\",\n itemListElement: items.map((it, i) => ({\n \"@type\": \"ListItem\",\n position: i + 1,\n name: it.name,\n item: it.url,\n })),\n };\n}\n\nexport function organizationSchema(input: {\n name: string;\n url: string;\n logo?: string;\n sameAs?: string[];\n}): JsonLd {\n return {\n \"@context\": \"https://schema.org\",\n \"@type\": \"Organization\",\n name: input.name,\n url: input.url,\n ...(input.logo ? { logo: input.logo } : {}),\n ...(input.sameAs && input.sameAs.length > 0 ? { sameAs: input.sameAs } : {}),\n };\n}\n\nexport function websiteSchema(input: {\n name: string;\n url: string;\n searchUrl?: string;\n}): JsonLd {\n return {\n \"@context\": \"https://schema.org\",\n \"@type\": \"WebSite\",\n name: input.name,\n url: input.url,\n ...(input.searchUrl\n ? {\n potentialAction: {\n \"@type\": \"SearchAction\",\n target: `${input.searchUrl}{search_term_string}`,\n \"query-input\": \"required name=search_term_string\",\n },\n }\n : {}),\n };\n}\n\nexport function faqSchema(\n qa: { question: string; answer: string }[],\n): JsonLd {\n return {\n \"@context\": \"https://schema.org\",\n \"@type\": \"FAQPage\",\n mainEntity: qa.map((x) => ({\n \"@type\": \"Question\",\n name: x.question,\n acceptedAnswer: { \"@type\": \"Answer\", text: x.answer },\n })),\n };\n}\n\n/** JSON-LD 객체 → `<script type=\"application/ld+json\">` HTML 문자열 (XSS-safe). */\nexport function jsonLdScript(schema: JsonLd | JsonLd[]): string {\n // `</script>` 주입 방지를 위해 `<` 이스케이프.\n const json = JSON.stringify(schema).replace(/</g, \"\\\\u003c\");\n return `<script type=\"application/ld+json\">${json}</script>`;\n}\n\nfunction toIso(value: string | Date | null | undefined): string | null {\n if (!value) return null;\n const d = value instanceof Date ? value : new Date(value);\n return Number.isNaN(d.getTime()) ? null : d.toISOString();\n}\n\nfunction escapeXml(value: string): string {\n return value\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\");\n}\n\nfunction cdata(value: string): string {\n return `<![CDATA[${value.replace(/]]>/g, \"]]]]><![CDATA[>\")}]]>`;\n}\n","/**\n * `@roottale/cms-client` — 리드(문의) 접수 server-side 헬퍼.\n *\n * 외부 고객사 사이트가 *블로그 조회(`fetchPosts`)와 동일한 API key 하나* 로 리드도\n * 접수하기 위한 SSR-only 클라이언트. 브라우저가 아니라 사이트 서버(Server Action /\n * Route Handler)에서 호출해야 한다 — API key 는 절대 브라우저로 노출 금지\n * (ADR-0023 §5.1 #15). 키가 tenant 를 권위 식별하므로 `tenant_slug` 불필요.\n *\n * 사용 예 (Next.js server action):\n * ```ts\n * \"use server\";\n * import { submitInquiry } from \"@roottale/cms-client/server\";\n *\n * const result = await submitInquiry({\n * apiKey: process.env.ROOTTALE_API_KEY!, // fetchPosts 와 같은 키\n * fields: {\n * vertical: \"tax\",\n * contactName: name,\n * businessName: company,\n * email,\n * phone,\n * message,\n * privacyConsent: true,\n * },\n * });\n * if (result.ok) showSuccess(); // 그 키의 tenant /crm 에 도착\n * else showError(result.message);\n * ```\n *\n * intake API 는 302 `?ok=1` / `?err=*` 로 응답 — 본 헬퍼가 파싱해 구조화 결과 반환.\n */\n\nconst DEFAULT_BASE_URL = \"https://api.roottale.com\";\n\nexport type InquiryVertical = \"consulting\" | \"medical\" | \"tax\" | \"legal\";\n\n/** intake API 의 `err` 코드 → 한국어 사용자 메시지. */\nexport const INQUIRY_ERROR_MESSAGES: Record<string, string> = {\n consent_privacy: \"개인정보 수집·이용 동의가 필요합니다.\",\n consent_overseas: \"의료 분야는 국외이전 동의가 필요합니다.\",\n invalid_vertical: \"업종 분류가 유효하지 않습니다.\",\n missing_fields: \"필수 항목이 비어 있습니다.\",\n invalid_email: \"이메일 형식이 올바르지 않습니다.\",\n invalid_api_key: \"연동 인증에 실패했습니다. API 키를 확인해주세요.\",\n internal: \"전송에 실패했습니다. 잠시 후 다시 시도해주세요.\",\n};\n\nexport interface SubmitInquiryFields {\n vertical: InquiryVertical;\n contactName: string;\n businessName: string;\n email: string;\n phone: string;\n /** 자유 문의 내용 (선택). */\n message?: string;\n /** 상담 분야 라벨 등 부가 메타 (선택). */\n consultationField?: string;\n /** 현재 사이트 URL (선택). */\n currentSiteUrl?: string;\n /** 개인정보 수집·이용 동의 (필수 true). */\n privacyConsent: boolean;\n /** 의료 vertical 의 국외이전 동의 (medical 일 때 필수). */\n overseasTransferConsent?: boolean;\n /**\n * \"patient\"(고객사 방문자 문의, 기본) | \"sales\"(RootTale 자체 B2B 영업 리드).\n * sales 는 medical 국외이전 동의 게이트 면제.\n */\n leadKind?: \"patient\" | \"sales\";\n /**\n * 표준 필드 외에 폼이 받은 임의 항목(key→value)을 있는 그대로 전달. intake 가\n * 암호화 보관하고 CRM 상세에서 노출한다. 예약 키(표준 필드)·`_` 접두 키는 무시.\n * 주의: 개인정보가 담길 수 있으므로 폼의 동의 고지에 수집 항목을 반영할 것.\n */\n extras?: Record<string, string>;\n}\n\n/** submitInquiry 가 전용으로 매핑하는 키 — extras 에서 충돌 시 무시. */\nconst RESERVED_INQUIRY_KEYS = new Set([\n \"vertical\",\n \"contact_name\",\n \"business_name\",\n \"email\",\n \"phone\",\n \"message\",\n \"consultation_field\",\n \"current_site_url\",\n \"privacy_consent\",\n \"overseas_transfer_consent\",\n \"lead_kind\",\n]);\n\nexport interface SubmitInquiryOptions {\n /** `rtlk_cust_*` API key — `fetchPosts` 와 동일한 키. Required. */\n apiKey: string;\n /** API base URL 오버라이드. 기본 `https://api.roottale.com`. */\n baseUrl?: string;\n fields: SubmitInquiryFields;\n signal?: AbortSignal;\n}\n\nexport type SubmitInquiryResult =\n | { ok: true }\n | { ok: false; code: string | null; message: string };\n\n/** `err` 코드를 사용자 메시지로 (미지의 코드는 generic). */\nexport function inquiryErrorMessage(code: string | null | undefined): string {\n return (code && INQUIRY_ERROR_MESSAGES[code]) || INQUIRY_ERROR_MESSAGES.internal;\n}\n\n/**\n * intake API 의 302 Location (`?ok=1` / `?err=*`) 을 구조화 결과로 파싱.\n * 외부 사이트가 ERROR_MESSAGES·파싱을 각자 재구현하지 않도록 공유.\n */\nexport function parseInquiryRedirect(\n location: string | null,\n base: string = DEFAULT_BASE_URL,\n): SubmitInquiryResult {\n if (!location) {\n return { ok: false, code: null, message: inquiryErrorMessage(null) };\n }\n let url: URL;\n try {\n url = new URL(location, base);\n } catch {\n return { ok: false, code: null, message: inquiryErrorMessage(null) };\n }\n if (url.searchParams.get(\"ok\") === \"1\") return { ok: true };\n const code = url.searchParams.get(\"err\");\n return { ok: false, code, message: inquiryErrorMessage(code) };\n}\n\nexport function formatKoreanPhoneNumber(input: string): string {\n let digits = input.replace(/\\D/g, \"\");\n if (digits.startsWith(\"82\") && digits.length >= 11) {\n digits = `0${digits.slice(2)}`;\n }\n if (digits.length === 11) {\n return `${digits.slice(0, 3)}-${digits.slice(3, 7)}-${digits.slice(7)}`;\n }\n if (digits.length === 10) {\n if (digits.startsWith(\"02\")) {\n return `${digits.slice(0, 2)}-${digits.slice(2, 6)}-${digits.slice(6)}`;\n }\n return `${digits.slice(0, 3)}-${digits.slice(3, 6)}-${digits.slice(6)}`;\n }\n if (digits.length === 8) {\n return `${digits.slice(0, 4)}-${digits.slice(4)}`;\n }\n return input.trim();\n}\n\n/**\n * 리드를 RootTale intake API 로 제출 (server-to-server, Bearer 인증).\n * 키의 tenant 로 라우팅되어 admin(mysite.roottale.com) /crm 에서 관리된다.\n */\nexport async function submitInquiry(\n options: SubmitInquiryOptions,\n): Promise<SubmitInquiryResult> {\n assertServer();\n const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n const f = options.fields;\n\n const body = new FormData();\n body.set(\"vertical\", f.vertical);\n body.set(\"contact_name\", f.contactName);\n body.set(\"business_name\", f.businessName);\n body.set(\"email\", f.email);\n body.set(\"phone\", formatKoreanPhoneNumber(f.phone));\n if (f.message) body.set(\"message\", f.message);\n if (f.consultationField) body.set(\"consultation_field\", f.consultationField);\n if (f.currentSiteUrl) body.set(\"current_site_url\", f.currentSiteUrl);\n // 사용자가 명시 동의한 경우에만 전달 (자동 부여 금지).\n if (f.privacyConsent) body.set(\"privacy_consent\", \"on\");\n if (f.overseasTransferConsent) body.set(\"overseas_transfer_consent\", \"on\");\n if (f.leadKind) body.set(\"lead_kind\", f.leadKind);\n // 표준 외 임의 항목 — 예약 키/`_`접두/빈 값/중복은 건너뛰고 원본 그대로 전달.\n if (f.extras) {\n for (const [rawKey, rawValue] of Object.entries(f.extras)) {\n const key = rawKey.trim();\n const value = String(rawValue ?? \"\").trim();\n if (!key || !value) continue;\n if (key.startsWith(\"_\") || RESERVED_INQUIRY_KEYS.has(key)) continue;\n if (body.has(key)) continue;\n body.set(key, value);\n }\n }\n\n let response: Response;\n try {\n response = await fetch(`${baseUrl}/v1/public/inquiries`, {\n method: \"POST\",\n headers: { authorization: `Bearer ${options.apiKey}` },\n body,\n // intake 는 302 로 결과 신호 — 자동 추적 금지하고 Location 직접 읽음.\n redirect: \"manual\",\n signal: options.signal,\n });\n } catch {\n return { ok: false, code: \"internal\", message: inquiryErrorMessage(\"internal\") };\n }\n\n // 잘못된/만료된 키 → 라우트가 401.\n if (response.status === 401) {\n return {\n ok: false,\n code: \"invalid_api_key\",\n message: inquiryErrorMessage(\"invalid_api_key\"),\n };\n }\n\n const location = response.headers.get(\"location\");\n if (location) return parseInquiryRedirect(location, baseUrl);\n\n // redirect 없이 성공 응답이면 ok 로 간주, 그 외엔 generic 실패.\n if (response.ok) return { ok: true };\n return { ok: false, code: \"internal\", message: inquiryErrorMessage(\"internal\") };\n}\n\nfunction assertServer(): void {\n if (typeof window !== \"undefined\") {\n throw new Error(\n \"@roottale/cms-client submitInquiry must not run in the browser. \" +\n \"Call it from a Server Action / Route Handler. \" +\n \"Shipping the API key to the browser breaks the security model (ADR-0023 §5.1 #15).\",\n );\n }\n}\n","/**\n * `@roottale/cms-client/server` — RootTale CMS Public API server-side fetch client.\n *\n * 외부 고객사 사이트가 RootTale CMS 콘텐츠를 fetch 하기 위한 SSR-only client.\n * Bearer (`rtlk_cust_*`) 인증, 브라우저 직접 호출 금지 (ADR-0023 §5.1 #15).\n *\n * 사용 예 (Next.js server component):\n * ```ts\n * import { fetchPosts } from \"@roottale/cms-client/server\";\n *\n * const posts = await fetchPosts({\n * apiKey: process.env.ROOTTALE_API_KEY!,\n * limit: 10,\n * });\n * ```\n *\n * 데이터 모델 = ADR-0034 `posts` (WP 7.0-aligned, type=post|page).\n * Phase 1 scope: list + get. WP import/export, draft preview = 별도 PR.\n */\n\nexport type CmsPostType = \"post\" | \"page\";\n\nexport type CmsTaxonomyKind = \"category\" | \"tag\" | \"custom\";\n\nexport interface CmsTermRef {\n id: string;\n taxonomy: CmsTaxonomyKind;\n slug: string;\n name: string;\n}\n\nexport interface CmsPostContent {\n /** UUIDv7. */\n id: string;\n /** Owning tenant id. */\n tenantId: string;\n /** Owning site id (ADR-0034 P4 dual-key). */\n siteId: string;\n /** WP post type. */\n type: CmsPostType;\n /** Lowercase kebab-case slug (1-120 chars). */\n slug: string;\n title: string;\n excerpt: string | null;\n /** Media id (resolved by renderer / consumer to URL). */\n featuredMediaId: string | null;\n /**\n * featured 이미지 delivery URL (CF Images variant, md 우선). 서버가 `featured_media_id`\n * → `media.variants_json` 를 해석해 제공. 대표 이미지 미설정 시 null.\n */\n featuredImageUrl: string | null;\n authorId: string | null;\n /** Public display name for the post author. Email is not exposed. */\n authorName: string | null;\n /** Public profile image URL for the post author. */\n authorImageUrl: string | null;\n /** Public biography text for the post author. */\n authorBio: string | null;\n /** Tiptap / Gutenberg-compatible Block JSON. Sanitization is renderer's responsibility. */\n bodyJson: Record<string, unknown>;\n /** Public API only exposes `published`. */\n status: \"published\";\n /** Per-post meta (SEO overrides, canonical, og, ...). */\n metaJson: Record<string, unknown>;\n /**\n * ADR-0041 — 포맷된 커스텀 필드(ACF) 값. 서버가 필드 정의(코드 ⊕ DB 그룹)로\n * `meta_json.acf` 를 포맷 — image/file/gallery 는 media variant URL 객체.\n * 해석된 그룹이 없거나 구 서버면 null.\n */\n fields: Record<string, unknown> | null;\n /**\n * Attached taxonomy terms (categories + tags + custom). Server 가 category →\n * tag → custom 순서로 정렬해 보내며, 각 taxonomy 내부는 name asc. attach 가 0 개면\n * 빈 배열. category 만 추출하려면 `terms.filter(t => t.taxonomy === \"category\")`.\n */\n terms: CmsTermRef[];\n /** ISO 8601. */\n publishedAt: string;\n /** ISO 8601. */\n createdAt: string;\n /** ISO 8601. */\n updatedAt: string;\n}\n\nexport interface CmsPostContentPage {\n items: CmsPostContent[];\n nextCursor: string | null;\n hasMore: boolean;\n}\n\nconst DEFAULT_BASE_URL = \"https://api.roottale.com\";\n\n// fleet 가시성 — 자기 버전을 자가 보고 (build-time 주입, tsup define).\n// api-core 가 사이트별 check-in 으로 기록 → internal `GET /v1/internal/sites`.\ndeclare const __CMS_CLIENT_VERSION__: string | undefined;\nconst CLIENT_VERSION =\n typeof __CMS_CLIENT_VERSION__ === \"string\" ? __CMS_CLIENT_VERSION__ : \"dev\";\n\n/** 설치된 cms-client 버전 (build-time 주입) — fleet well-known 라우트 등에서 사용. */\nexport const CMS_CLIENT_VERSION = CLIENT_VERSION;\n\nfunction authHeaders(apiKey: string): Record<string, string> {\n return {\n authorization: `Bearer ${apiKey}`,\n \"x-roottale-client\": `cms-client/${CLIENT_VERSION}`,\n };\n}\n\nexport interface FetchPostsOptions {\n /** `rtlk_cust_*` API key. Required. */\n apiKey: string;\n /** Override the API base URL. Defaults to `https://api.roottale.com`. */\n baseUrl?: string;\n /** Page size, 1-100, default 20. */\n limit?: number;\n /** Opaque pagination cursor returned from previous call. */\n cursor?: string;\n /** Filter by post type (`post` or `page`). Default = both. */\n type?: CmsPostType;\n /** Override the auto-resolved site id (Phase 1 = 1 site/tenant, usually omitted). */\n siteId?: string;\n /** Optional AbortSignal. */\n signal?: AbortSignal;\n}\n\nexport interface FetchPostOptions {\n apiKey: string;\n baseUrl?: string;\n /** Slug (kebab-case) or UUIDv7 id. */\n slugOrId: string;\n /** Override the auto-resolved site id. */\n siteId?: string;\n signal?: AbortSignal;\n}\n\n/**\n * 외부 사이트가 cms-renderer 에 주입할 RootTale 디자인 토큰.\n * admin /design (mysite.roottale.com) 에서 저장한 값과 동일한 shape.\n * 미설정 필드는 응답에서 누락 → renderer 의 CSS fallback 이 사용된다.\n */\nexport interface RootTaleTheme {\n colors?: {\n primary?: string;\n primaryForeground?: string;\n foreground?: string;\n background?: string;\n secondary?: string;\n secondaryForeground?: string;\n accent?: string;\n accentForeground?: string;\n muted?: string;\n mutedForeground?: string;\n destructive?: string;\n destructiveForeground?: string;\n border?: string;\n input?: string;\n ring?: string;\n };\n fonts?: {\n body?: string;\n display?: string;\n };\n radius?: {\n sm?: string;\n md?: string;\n lg?: string;\n xl?: string;\n };\n}\n\nexport interface FetchThemeOptions {\n apiKey: string;\n baseUrl?: string;\n siteId?: string;\n signal?: AbortSignal;\n}\n\n/**\n * tenant 의 RootTale 디자인 토큰을 가져온다. admin /design 의 마지막 저장값\n * (없으면 빈 객체) — renderer 가 이 값을 CSS 변수로 주입한다.\n *\n * 자동 fetch (renderer 가 theme prop 없을 때) 와 명시 호출 모두 지원.\n * 네트워크/서버 에러는 throw — 호출자가 fallback 처리.\n */\nexport async function fetchTheme(options: FetchThemeOptions): Promise<RootTaleTheme> {\n assertServer();\n const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n const params = new URLSearchParams();\n if (options.siteId) params.set(\"site_id\", options.siteId);\n const qs = params.toString();\n const url = `${baseUrl}/v1/cms/public/theme${qs ? `?${qs}` : \"\"}`;\n\n const response = await fetch(url, {\n method: \"GET\",\n headers: authHeaders(options.apiKey),\n signal: options.signal,\n });\n\n if (!response.ok) {\n throw await toApiError(response);\n }\n\n const json = (await response.json()) as {\n colors?: RootTaleTheme[\"colors\"];\n fonts?: RootTaleTheme[\"fonts\"];\n radius?: RootTaleTheme[\"radius\"];\n };\n const theme: RootTaleTheme = {};\n if (json.colors) theme.colors = json.colors;\n if (json.fonts) theme.fonts = json.fonts;\n if (json.radius) theme.radius = json.radius;\n return theme;\n}\n\nexport type AnalyticsTagProvider = \"ga4\" | \"clarity\" | \"meta_pixel\" | \"naver\";\n\n/** analytics-runtime `TagConfig` 와 구조 호환 — `buildManifest` 입력. */\nexport interface AnalyticsTagConfig {\n provider: AnalyticsTagProvider;\n id: string;\n enabled: boolean;\n}\n\nexport interface SiteAnalyticsTags {\n siteId: string;\n tags: AnalyticsTagConfig[];\n}\n\nexport interface FetchAnalyticsOptions {\n apiKey: string;\n baseUrl?: string;\n siteId?: string;\n signal?: AbortSignal;\n}\n\n/**\n * ADR-0038 plane ① — 외부 사이트가 `<head>` 에 주입할 런타임 태그(GA4 measurement\n * id / Clarity project id 등, 비밀 아님). RootTale 관리용 default + 테넌트 고객용\n * 설정을 머지한 결과를 반환한다. analytics-runtime `buildManifest` 입력으로 쓰고,\n * 동의는 항상 fail-closed(런타임이 처리)이므로 키 노출 위험 없음(public id).\n */\nexport async function fetchAnalyticsConfig(\n options: FetchAnalyticsOptions,\n): Promise<SiteAnalyticsTags> {\n assertServer();\n const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n const params = new URLSearchParams();\n if (options.siteId) params.set(\"site_id\", options.siteId);\n const qs = params.toString();\n const url = `${baseUrl}/v1/cms/public/analytics${qs ? `?${qs}` : \"\"}`;\n\n const response = await fetch(url, {\n method: \"GET\",\n headers: authHeaders(options.apiKey),\n signal: options.signal,\n });\n\n if (!response.ok) {\n throw await toApiError(response);\n }\n\n const json = (await response.json()) as {\n site_id: string;\n tags?: AnalyticsTagConfig[];\n };\n return { siteId: json.site_id, tags: json.tags ?? [] };\n}\n\n/**\n * 블로그 표시 설정 — admin /design 의 \"블로그 표시\" 에서 저장한 사이트 기본값.\n * renderer 가 prop 미지정 시 이 값을 기본으로 적용 (글별 override 는 post 의\n * metaJson.display 가 우선).\n */\nexport interface RootTaleBlogSettings {\n showTableOfContents: boolean;\n showAuthor: boolean;\n showDate: boolean;\n showAuthorCard: boolean;\n /** 목차 상단 제목. null = renderer 기본(\"목차\"). */\n tocTitle: string | null;\n /** 작성자 카드 상단 라벨. null = renderer 기본(\"작성자\"). */\n authorCardEyebrow: string | null;\n /** 작성자 카드 설명. null = 설명 없음. */\n authorCardDescription: string | null;\n /** 작성자 프로필 이름. null = 글 작성자 이름 사용. */\n authorProfileName: string | null;\n /** 작성자 프로필 bio. null = 설명 없음. */\n authorProfileBio: string | null;\n /** 작성자 프로필 이미지 URL. null = 이니셜 아바타 사용. */\n authorProfileImageUrl: string | null;\n /** 작성자 프로필 이미지 모서리. */\n authorProfileImageRadius: \"circle\" | \"rounded\" | \"square\";\n /** 작성자 프로필 이미지 object-position x (%). */\n authorProfileImagePositionX: number;\n /** 작성자 프로필 이미지 object-position y (%). */\n authorProfileImagePositionY: number;\n}\n\nexport interface FetchBlogSettingsOptions {\n apiKey: string;\n baseUrl?: string;\n siteId?: string;\n signal?: AbortSignal;\n}\n\n/**\n * tenant 의 블로그 표시 기본값을 가져온다. 네트워크/서버 에러는 throw —\n * 호출자(renderer)가 안전 기본값으로 fallback.\n */\nexport async function fetchBlogSettings(\n options: FetchBlogSettingsOptions,\n): Promise<RootTaleBlogSettings> {\n assertServer();\n const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n const params = new URLSearchParams();\n if (options.siteId) params.set(\"site_id\", options.siteId);\n const qs = params.toString();\n const url = `${baseUrl}/v1/cms/public/blog-settings${qs ? `?${qs}` : \"\"}`;\n\n const response = await fetch(url, {\n method: \"GET\",\n headers: authHeaders(options.apiKey),\n signal: options.signal,\n });\n\n if (!response.ok) {\n throw await toApiError(response);\n }\n\n const json = (await response.json()) as {\n show_table_of_contents?: boolean;\n show_author?: boolean;\n show_date?: boolean;\n show_author_card?: boolean;\n toc_title?: string | null;\n author_card_eyebrow?: string | null;\n author_card_description?: string | null;\n author_profile_name?: string | null;\n author_profile_bio?: string | null;\n author_profile_image_url?: string | null;\n author_profile_image_radius?: string | null;\n author_profile_image_position_x?: number;\n author_profile_image_position_y?: number;\n };\n const radius =\n json.author_profile_image_radius === \"rounded\" ||\n json.author_profile_image_radius === \"square\"\n ? json.author_profile_image_radius\n : \"circle\";\n const percent = (x: unknown): number =>\n typeof x === \"number\" && Number.isFinite(x)\n ? Math.max(0, Math.min(100, Math.round(x)))\n : 50;\n return {\n showTableOfContents: json.show_table_of_contents === true,\n showAuthor: json.show_author !== false,\n showDate: json.show_date !== false,\n showAuthorCard: json.show_author_card !== false,\n tocTitle:\n typeof json.toc_title === \"string\" && json.toc_title.length > 0\n ? json.toc_title\n : null,\n authorCardEyebrow:\n typeof json.author_card_eyebrow === \"string\" &&\n json.author_card_eyebrow.length > 0\n ? json.author_card_eyebrow\n : null,\n authorCardDescription:\n typeof json.author_card_description === \"string\" &&\n json.author_card_description.length > 0\n ? json.author_card_description\n : null,\n authorProfileName:\n typeof json.author_profile_name === \"string\" &&\n json.author_profile_name.length > 0\n ? json.author_profile_name\n : null,\n authorProfileBio:\n typeof json.author_profile_bio === \"string\" &&\n json.author_profile_bio.length > 0\n ? json.author_profile_bio\n : null,\n authorProfileImageUrl:\n typeof json.author_profile_image_url === \"string\" &&\n json.author_profile_image_url.length > 0\n ? json.author_profile_image_url\n : null,\n authorProfileImageRadius: radius,\n authorProfileImagePositionX: percent(json.author_profile_image_position_x),\n authorProfileImagePositionY: percent(json.author_profile_image_position_y),\n };\n}\n\n/** fetch 실패/미설정 시 사용할 안전 기본값 (기존 렌더러 동작). */\nexport const DEFAULT_BLOG_SETTINGS: RootTaleBlogSettings = {\n showTableOfContents: false,\n showAuthor: true,\n showDate: true,\n showAuthorCard: true,\n tocTitle: null,\n authorCardEyebrow: null,\n authorCardDescription: null,\n authorProfileName: null,\n authorProfileBio: null,\n authorProfileImageUrl: null,\n authorProfileImageRadius: \"circle\",\n authorProfileImagePositionX: 50,\n authorProfileImagePositionY: 50,\n};\n\nexport interface ResolvedPostDisplay {\n showTableOfContents: boolean;\n showAuthor: boolean;\n showDate: boolean;\n showAuthorCard: boolean;\n tocTitle: string;\n authorCardEyebrow: string;\n authorCardDescription: string | null;\n authorProfileName: string | null;\n authorProfileBio: string | null;\n authorProfileImageUrl: string | null;\n authorProfileImageRadius: \"circle\" | \"rounded\" | \"square\";\n authorProfileImagePositionX: number;\n authorProfileImagePositionY: number;\n}\n\n/**\n * 글 1건의 실제 표시 여부 결정. 우선순위:\n * 명시 prop > 글별 override(post.metaJson.display) > 사이트 기본값(settings).\n * tocTitle: 명시 prop > settings.tocTitle > \"목차\".\n */\nexport function resolvePostDisplay(\n post: { metaJson?: Record<string, unknown> | null },\n settings: RootTaleBlogSettings,\n explicit?: { showTableOfContents?: boolean; tableOfContentsTitle?: string },\n): ResolvedPostDisplay {\n const display =\n post.metaJson && typeof post.metaJson === \"object\"\n ? ((post.metaJson as Record<string, unknown>).display as\n | Record<string, unknown>\n | undefined)\n : undefined;\n const ov = (key: string): boolean | undefined =>\n display && typeof display[key] === \"boolean\"\n ? (display[key] as boolean)\n : undefined;\n return {\n showTableOfContents:\n explicit?.showTableOfContents ?? ov(\"toc\") ?? settings.showTableOfContents,\n showAuthor: ov(\"author\") ?? settings.showAuthor,\n showDate: ov(\"date\") ?? settings.showDate,\n showAuthorCard: settings.showAuthorCard,\n tocTitle: explicit?.tableOfContentsTitle ?? settings.tocTitle ?? \"목차\",\n authorCardEyebrow: settings.authorCardEyebrow ?? \"작성자\",\n authorCardDescription: settings.authorCardDescription,\n authorProfileName: settings.authorProfileName,\n authorProfileBio: settings.authorProfileBio,\n authorProfileImageUrl: settings.authorProfileImageUrl,\n authorProfileImageRadius: settings.authorProfileImageRadius,\n authorProfileImagePositionX: settings.authorProfileImagePositionX,\n authorProfileImagePositionY: settings.authorProfileImagePositionY,\n };\n}\n\nexport class CmsApiError extends Error {\n readonly status: number;\n readonly code: string;\n constructor(message: string, status: number, code: string) {\n super(message);\n this.name = \"CmsApiError\";\n this.status = status;\n this.code = code;\n }\n}\n\nexport async function fetchPosts(options: FetchPostsOptions): Promise<CmsPostContentPage> {\n assertServer();\n const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n const params = new URLSearchParams();\n if (options.limit !== undefined) params.set(\"limit\", String(options.limit));\n if (options.cursor) params.set(\"cursor\", options.cursor);\n if (options.type) params.set(\"type\", options.type);\n if (options.siteId) params.set(\"site_id\", options.siteId);\n const qs = params.toString();\n const url = `${baseUrl}/v1/cms/public/posts${qs ? `?${qs}` : \"\"}`;\n\n const response = await fetch(url, {\n method: \"GET\",\n headers: authHeaders(options.apiKey),\n signal: options.signal,\n });\n\n if (!response.ok) {\n throw await toApiError(response);\n }\n\n const json = (await response.json()) as {\n items: CmsPostWire[];\n has_more: boolean;\n next_cursor: string | null;\n };\n return {\n items: json.items.map(fromWire),\n hasMore: json.has_more,\n nextCursor: json.next_cursor,\n };\n}\n\nexport async function fetchPost(options: FetchPostOptions): Promise<CmsPostContent | null> {\n assertServer();\n const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n const params = new URLSearchParams();\n if (options.siteId) params.set(\"site_id\", options.siteId);\n const qs = params.toString();\n const url = `${baseUrl}/v1/cms/public/posts/${encodeURIComponent(options.slugOrId)}${qs ? `?${qs}` : \"\"}`;\n\n const response = await fetch(url, {\n method: \"GET\",\n headers: authHeaders(options.apiKey),\n signal: options.signal,\n });\n\n if (response.status === 404) return null;\n if (!response.ok) {\n throw await toApiError(response);\n }\n\n const json = (await response.json()) as CmsPostWire;\n return fromWire(json);\n}\n\ntype CmsPostWire = {\n id: string;\n tenant_id: string;\n site_id: string;\n type: CmsPostType;\n slug: string;\n title: string;\n excerpt: string | null;\n featured_media_id: string | null;\n /** featured 이미지 delivery URL. 구 서버는 미포함 — backward-compat optional. */\n featured_media_url?: string | null;\n author_id: string | null;\n author_name?: string | null;\n author_image_url?: string | null;\n author_bio?: string | null;\n body_json: Record<string, unknown>;\n status: \"published\";\n meta_json: Record<string, unknown>;\n /** ADR-0041 포맷 필드. 구 서버는 미포함 — backward-compat optional. */\n fields?: Record<string, unknown> | null;\n /** 0.2.0 이전 서버는 미포함 — backward-compat 차원에서 optional. */\n terms?: CmsTermRef[];\n published_at: string;\n created_at: string;\n updated_at: string;\n};\n\nfunction fromWire(wire: CmsPostWire): CmsPostContent {\n return {\n id: wire.id,\n tenantId: wire.tenant_id,\n siteId: wire.site_id,\n type: wire.type,\n slug: wire.slug,\n title: wire.title,\n excerpt: wire.excerpt,\n featuredMediaId: wire.featured_media_id,\n featuredImageUrl: wire.featured_media_url ?? null,\n authorId: wire.author_id,\n authorName: wire.author_name ?? null,\n authorImageUrl: wire.author_image_url ?? null,\n authorBio: wire.author_bio ?? null,\n bodyJson: wire.body_json,\n status: wire.status,\n metaJson: wire.meta_json,\n fields: wire.fields ?? null,\n terms: wire.terms ?? [],\n publishedAt: wire.published_at,\n createdAt: wire.created_at,\n updatedAt: wire.updated_at,\n };\n}\n\nasync function toApiError(response: Response): Promise<CmsApiError> {\n let code = \"request_failed\";\n let message = `RootTale CMS API error: ${response.status}`;\n try {\n const body = (await response.json()) as { code?: string; message?: string };\n if (body.code) code = body.code;\n if (body.message) message = body.message;\n } catch {\n /* ignore body parse */\n }\n return new CmsApiError(message, response.status, code);\n}\n\nfunction assertServer(): void {\n if (typeof window !== \"undefined\") {\n throw new Error(\n \"@roottale/cms-client/server must not run in the browser. \" +\n \"Move this call to a Server Component, getServerSideProps, or a Route Handler. \" +\n \"Shipping API keys to the browser breaks the security model (ADR-0023 §5.1 #15).\",\n );\n }\n}\n\n// 프레임워크 무관 SEO 헬퍼 (RSS 2.0 + Schema.org JSON-LD) 재노출.\nexport * from \"./seo\";\n\n// 리드 접수 — 블로그 조회와 동일한 API key 하나로 server-side 제출 (submitInquiry).\nexport * from \"./inquiry\";\n"],"mappings":";AAiCO,SAAS,eAAe,OAA6B;AAC1D,QAAM,OAAO,MAAM,YAAY;AAC/B,QAAM,YACJ,MAAM,MAAM,SAAS,IAAI,SAAS,MAAM,MAAM,CAAC,EAAG,OAAO,IAAI;AAC/D,QAAM,QAAkB,CAAC;AACzB,QAAM,KAAK,wCAAwC;AACnD,QAAM;AAAA,IACJ;AAAA,EACF;AACA,QAAM,KAAK,WAAW;AACtB,QAAM,KAAK,UAAU,UAAU,MAAM,KAAK,CAAC,UAAU;AACrD,QAAM,KAAK,SAAS,UAAU,MAAM,IAAI,CAAC,SAAS;AAClD,QAAM;AAAA,IACJ,oBAAoB,UAAU,MAAM,OAAO,CAAC;AAAA,EAC9C;AACA,QAAM;AAAA,IACJ,gBAAgB,UAAU,MAAM,eAAe,MAAM,KAAK,CAAC;AAAA,EAC7D;AACA,QAAM,KAAK,aAAa,UAAU,IAAI,CAAC,aAAa;AACpD,MAAI,UAAW,OAAM,KAAK,kBAAkB,SAAS,kBAAkB;AACvE,aAAW,QAAQ,MAAM,OAAO;AAC9B,UAAM,OAAO,KAAK,QAAQ,KAAK;AAC/B,UAAM,KAAK,QAAQ;AACnB,UAAM,KAAK,UAAU,UAAU,KAAK,KAAK,CAAC,UAAU;AACpD,UAAM,KAAK,SAAS,UAAU,KAAK,IAAI,CAAC,SAAS;AACjD,UAAM,KAAK,6BAA6B,UAAU,IAAI,CAAC,SAAS;AAChE,QAAI,KAAK,aAAa;AACpB,YAAM,KAAK,gBAAgB,MAAM,KAAK,WAAW,CAAC,gBAAgB;AAAA,IACpE;AACA,UAAM,MAAM,SAAS,KAAK,OAAO;AACjC,QAAI,IAAK,OAAM,KAAK,YAAY,GAAG,YAAY;AAC/C,UAAM,KAAK,SAAS;AAAA,EACtB;AACA,QAAM,KAAK,YAAY;AACvB,QAAM,KAAK,QAAQ;AACnB,SAAO,MAAM,KAAK,IAAI;AACxB;AAGO,SAAS,kBACd,OACA,MACgB;AAChB,SAAO,MAAM,IAAI,CAAC,OAAO;AAAA,IACvB,OAAO,EAAE;AAAA,IACT,MAAM,KAAK,QAAQ,CAAC;AAAA,IACpB,MAAM,KAAK,QAAQ,CAAC;AAAA,IACpB,aAAa,EAAE;AAAA,IACf,SAAS,EAAE;AAAA,EACb,EAAE;AACJ;AAEA,SAAS,SAAS,OAAiD;AACjE,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,IAAI,iBAAiB,OAAO,QAAQ,IAAI,KAAK,KAAK;AACxD,SAAO,OAAO,MAAM,EAAE,QAAQ,CAAC,IAAI,KAAK,EAAE,YAAY;AACxD;AAkBO,SAAS,cAAc,OAAmC;AAC/D,QAAM,SAAiB;AAAA,IACrB,YAAY;AAAA,IACZ,SAAS;AAAA,IACT,UAAU,MAAM;AAAA,IAChB,kBAAkB,EAAE,SAAS,WAAW,OAAO,MAAM,IAAI;AAAA,EAC3D;AACA,MAAI,MAAM,YAAa,QAAO,cAAc,MAAM;AAClD,MAAI,MAAM,MAAO,QAAO,QAAQ,MAAM;AACtC,QAAM,MAAM,MAAM,MAAM,aAAa;AACrC,MAAI,IAAK,QAAO,gBAAgB;AAChC,QAAM,MAAM,MAAM,MAAM,YAAY;AACpC,MAAI,IAAK,QAAO,eAAe;AAC/B,MAAI,MAAM;AACR,WAAO,SAAS,EAAE,SAAS,UAAU,MAAM,MAAM,WAAW;AAC9D,MAAI,MAAM,eAAe;AACvB,WAAO,YAAY;AAAA,MACjB,SAAS;AAAA,MACT,MAAM,MAAM;AAAA,MACZ,GAAI,MAAM,gBACN,EAAE,MAAM,EAAE,SAAS,eAAe,KAAK,MAAM,cAAc,EAAE,IAC7D,CAAC;AAAA,IACP;AAAA,EACF;AACA,SAAO;AACT;AAEO,SAAS,iBACd,OACQ;AACR,SAAO;AAAA,IACL,YAAY;AAAA,IACZ,SAAS;AAAA,IACT,iBAAiB,MAAM,IAAI,CAAC,IAAI,OAAO;AAAA,MACrC,SAAS;AAAA,MACT,UAAU,IAAI;AAAA,MACd,MAAM,GAAG;AAAA,MACT,MAAM,GAAG;AAAA,IACX,EAAE;AAAA,EACJ;AACF;AAEO,SAAS,mBAAmB,OAKxB;AACT,SAAO;AAAA,IACL,YAAY;AAAA,IACZ,SAAS;AAAA,IACT,MAAM,MAAM;AAAA,IACZ,KAAK,MAAM;AAAA,IACX,GAAI,MAAM,OAAO,EAAE,MAAM,MAAM,KAAK,IAAI,CAAC;AAAA,IACzC,GAAI,MAAM,UAAU,MAAM,OAAO,SAAS,IAAI,EAAE,QAAQ,MAAM,OAAO,IAAI,CAAC;AAAA,EAC5E;AACF;AAEO,SAAS,cAAc,OAInB;AACT,SAAO;AAAA,IACL,YAAY;AAAA,IACZ,SAAS;AAAA,IACT,MAAM,MAAM;AAAA,IACZ,KAAK,MAAM;AAAA,IACX,GAAI,MAAM,YACN;AAAA,MACE,iBAAiB;AAAA,QACf,SAAS;AAAA,QACT,QAAQ,GAAG,MAAM,SAAS;AAAA,QAC1B,eAAe;AAAA,MACjB;AAAA,IACF,IACA,CAAC;AAAA,EACP;AACF;AAEO,SAAS,UACd,IACQ;AACR,SAAO;AAAA,IACL,YAAY;AAAA,IACZ,SAAS;AAAA,IACT,YAAY,GAAG,IAAI,CAAC,OAAO;AAAA,MACzB,SAAS;AAAA,MACT,MAAM,EAAE;AAAA,MACR,gBAAgB,EAAE,SAAS,UAAU,MAAM,EAAE,OAAO;AAAA,IACtD,EAAE;AAAA,EACJ;AACF;AAGO,SAAS,aAAa,QAAmC;AAE9D,QAAM,OAAO,KAAK,UAAU,MAAM,EAAE,QAAQ,MAAM,SAAS;AAC3D,SAAO,sCAAsC,IAAI;AACnD;AAEA,SAAS,MAAM,OAAwD;AACrE,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,IAAI,iBAAiB,OAAO,QAAQ,IAAI,KAAK,KAAK;AACxD,SAAO,OAAO,MAAM,EAAE,QAAQ,CAAC,IAAI,OAAO,EAAE,YAAY;AAC1D;AAEA,SAAS,UAAU,OAAuB;AACxC,SAAO,MACJ,QAAQ,MAAM,OAAO,EACrB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,QAAQ;AAC3B;AAEA,SAAS,MAAM,OAAuB;AACpC,SAAO,YAAY,MAAM,QAAQ,QAAQ,iBAAiB,CAAC;AAC7D;;;AChMA,IAAM,mBAAmB;AAKlB,IAAM,yBAAiD;AAAA,EAC5D,iBAAiB;AAAA,EACjB,kBAAkB;AAAA,EAClB,kBAAkB;AAAA,EAClB,gBAAgB;AAAA,EAChB,eAAe;AAAA,EACf,iBAAiB;AAAA,EACjB,UAAU;AACZ;AAgCA,IAAM,wBAAwB,oBAAI,IAAI;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAgBM,SAAS,oBAAoB,MAAyC;AAC3E,SAAQ,QAAQ,uBAAuB,IAAI,KAAM,uBAAuB;AAC1E;AAMO,SAAS,qBACd,UACA,OAAe,kBACM;AACrB,MAAI,CAAC,UAAU;AACb,WAAO,EAAE,IAAI,OAAO,MAAM,MAAM,SAAS,oBAAoB,IAAI,EAAE;AAAA,EACrE;AACA,MAAI;AACJ,MAAI;AACF,UAAM,IAAI,IAAI,UAAU,IAAI;AAAA,EAC9B,QAAQ;AACN,WAAO,EAAE,IAAI,OAAO,MAAM,MAAM,SAAS,oBAAoB,IAAI,EAAE;AAAA,EACrE;AACA,MAAI,IAAI,aAAa,IAAI,IAAI,MAAM,IAAK,QAAO,EAAE,IAAI,KAAK;AAC1D,QAAM,OAAO,IAAI,aAAa,IAAI,KAAK;AACvC,SAAO,EAAE,IAAI,OAAO,MAAM,SAAS,oBAAoB,IAAI,EAAE;AAC/D;AAEO,SAAS,wBAAwB,OAAuB;AAC7D,MAAI,SAAS,MAAM,QAAQ,OAAO,EAAE;AACpC,MAAI,OAAO,WAAW,IAAI,KAAK,OAAO,UAAU,IAAI;AAClD,aAAS,IAAI,OAAO,MAAM,CAAC,CAAC;AAAA,EAC9B;AACA,MAAI,OAAO,WAAW,IAAI;AACxB,WAAO,GAAG,OAAO,MAAM,GAAG,CAAC,CAAC,IAAI,OAAO,MAAM,GAAG,CAAC,CAAC,IAAI,OAAO,MAAM,CAAC,CAAC;AAAA,EACvE;AACA,MAAI,OAAO,WAAW,IAAI;AACxB,QAAI,OAAO,WAAW,IAAI,GAAG;AAC3B,aAAO,GAAG,OAAO,MAAM,GAAG,CAAC,CAAC,IAAI,OAAO,MAAM,GAAG,CAAC,CAAC,IAAI,OAAO,MAAM,CAAC,CAAC;AAAA,IACvE;AACA,WAAO,GAAG,OAAO,MAAM,GAAG,CAAC,CAAC,IAAI,OAAO,MAAM,GAAG,CAAC,CAAC,IAAI,OAAO,MAAM,CAAC,CAAC;AAAA,EACvE;AACA,MAAI,OAAO,WAAW,GAAG;AACvB,WAAO,GAAG,OAAO,MAAM,GAAG,CAAC,CAAC,IAAI,OAAO,MAAM,CAAC,CAAC;AAAA,EACjD;AACA,SAAO,MAAM,KAAK;AACpB;AAMA,eAAsB,cACpB,SAC8B;AAC9B,eAAa;AACb,QAAM,WAAW,QAAQ,WAAW,kBAAkB,QAAQ,QAAQ,EAAE;AACxE,QAAM,IAAI,QAAQ;AAElB,QAAM,OAAO,IAAI,SAAS;AAC1B,OAAK,IAAI,YAAY,EAAE,QAAQ;AAC/B,OAAK,IAAI,gBAAgB,EAAE,WAAW;AACtC,OAAK,IAAI,iBAAiB,EAAE,YAAY;AACxC,OAAK,IAAI,SAAS,EAAE,KAAK;AACzB,OAAK,IAAI,SAAS,wBAAwB,EAAE,KAAK,CAAC;AAClD,MAAI,EAAE,QAAS,MAAK,IAAI,WAAW,EAAE,OAAO;AAC5C,MAAI,EAAE,kBAAmB,MAAK,IAAI,sBAAsB,EAAE,iBAAiB;AAC3E,MAAI,EAAE,eAAgB,MAAK,IAAI,oBAAoB,EAAE,cAAc;AAEnE,MAAI,EAAE,eAAgB,MAAK,IAAI,mBAAmB,IAAI;AACtD,MAAI,EAAE,wBAAyB,MAAK,IAAI,6BAA6B,IAAI;AACzE,MAAI,EAAE,SAAU,MAAK,IAAI,aAAa,EAAE,QAAQ;AAEhD,MAAI,EAAE,QAAQ;AACZ,eAAW,CAAC,QAAQ,QAAQ,KAAK,OAAO,QAAQ,EAAE,MAAM,GAAG;AACzD,YAAM,MAAM,OAAO,KAAK;AACxB,YAAM,QAAQ,OAAO,YAAY,EAAE,EAAE,KAAK;AAC1C,UAAI,CAAC,OAAO,CAAC,MAAO;AACpB,UAAI,IAAI,WAAW,GAAG,KAAK,sBAAsB,IAAI,GAAG,EAAG;AAC3D,UAAI,KAAK,IAAI,GAAG,EAAG;AACnB,WAAK,IAAI,KAAK,KAAK;AAAA,IACrB;AAAA,EACF;AAEA,MAAI;AACJ,MAAI;AACF,eAAW,MAAM,MAAM,GAAG,OAAO,wBAAwB;AAAA,MACvD,QAAQ;AAAA,MACR,SAAS,EAAE,eAAe,UAAU,QAAQ,MAAM,GAAG;AAAA,MACrD;AAAA;AAAA,MAEA,UAAU;AAAA,MACV,QAAQ,QAAQ;AAAA,IAClB,CAAC;AAAA,EACH,QAAQ;AACN,WAAO,EAAE,IAAI,OAAO,MAAM,YAAY,SAAS,oBAAoB,UAAU,EAAE;AAAA,EACjF;AAGA,MAAI,SAAS,WAAW,KAAK;AAC3B,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,SAAS,oBAAoB,iBAAiB;AAAA,IAChD;AAAA,EACF;AAEA,QAAM,WAAW,SAAS,QAAQ,IAAI,UAAU;AAChD,MAAI,SAAU,QAAO,qBAAqB,UAAU,OAAO;AAG3D,MAAI,SAAS,GAAI,QAAO,EAAE,IAAI,KAAK;AACnC,SAAO,EAAE,IAAI,OAAO,MAAM,YAAY,SAAS,oBAAoB,UAAU,EAAE;AACjF;AAEA,SAAS,eAAqB;AAC5B,MAAI,OAAO,WAAW,aAAa;AACjC,UAAM,IAAI;AAAA,MACR;AAAA,IAGF;AAAA,EACF;AACF;;;ACxIA,IAAMA,oBAAmB;AAKzB,IAAM,iBACJ,OAA6C,WAAyB;AAGjE,IAAM,qBAAqB;AAElC,SAAS,YAAY,QAAwC;AAC3D,SAAO;AAAA,IACL,eAAe,UAAU,MAAM;AAAA,IAC/B,qBAAqB,cAAc,cAAc;AAAA,EACnD;AACF;AA8EA,eAAsB,WAAW,SAAoD;AACnF,EAAAC,cAAa;AACb,QAAM,WAAW,QAAQ,WAAWD,mBAAkB,QAAQ,QAAQ,EAAE;AACxE,QAAM,SAAS,IAAI,gBAAgB;AACnC,MAAI,QAAQ,OAAQ,QAAO,IAAI,WAAW,QAAQ,MAAM;AACxD,QAAM,KAAK,OAAO,SAAS;AAC3B,QAAM,MAAM,GAAG,OAAO,uBAAuB,KAAK,IAAI,EAAE,KAAK,EAAE;AAE/D,QAAM,WAAW,MAAM,MAAM,KAAK;AAAA,IAChC,QAAQ;AAAA,IACR,SAAS,YAAY,QAAQ,MAAM;AAAA,IACnC,QAAQ,QAAQ;AAAA,EAClB,CAAC;AAED,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,MAAM,WAAW,QAAQ;AAAA,EACjC;AAEA,QAAM,OAAQ,MAAM,SAAS,KAAK;AAKlC,QAAM,QAAuB,CAAC;AAC9B,MAAI,KAAK,OAAQ,OAAM,SAAS,KAAK;AACrC,MAAI,KAAK,MAAO,OAAM,QAAQ,KAAK;AACnC,MAAI,KAAK,OAAQ,OAAM,SAAS,KAAK;AACrC,SAAO;AACT;AA6BA,eAAsB,qBACpB,SAC4B;AAC5B,EAAAC,cAAa;AACb,QAAM,WAAW,QAAQ,WAAWD,mBAAkB,QAAQ,QAAQ,EAAE;AACxE,QAAM,SAAS,IAAI,gBAAgB;AACnC,MAAI,QAAQ,OAAQ,QAAO,IAAI,WAAW,QAAQ,MAAM;AACxD,QAAM,KAAK,OAAO,SAAS;AAC3B,QAAM,MAAM,GAAG,OAAO,2BAA2B,KAAK,IAAI,EAAE,KAAK,EAAE;AAEnE,QAAM,WAAW,MAAM,MAAM,KAAK;AAAA,IAChC,QAAQ;AAAA,IACR,SAAS,YAAY,QAAQ,MAAM;AAAA,IACnC,QAAQ,QAAQ;AAAA,EAClB,CAAC;AAED,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,MAAM,WAAW,QAAQ;AAAA,EACjC;AAEA,QAAM,OAAQ,MAAM,SAAS,KAAK;AAIlC,SAAO,EAAE,QAAQ,KAAK,SAAS,MAAM,KAAK,QAAQ,CAAC,EAAE;AACvD;AA2CA,eAAsB,kBACpB,SAC+B;AAC/B,EAAAC,cAAa;AACb,QAAM,WAAW,QAAQ,WAAWD,mBAAkB,QAAQ,QAAQ,EAAE;AACxE,QAAM,SAAS,IAAI,gBAAgB;AACnC,MAAI,QAAQ,OAAQ,QAAO,IAAI,WAAW,QAAQ,MAAM;AACxD,QAAM,KAAK,OAAO,SAAS;AAC3B,QAAM,MAAM,GAAG,OAAO,+BAA+B,KAAK,IAAI,EAAE,KAAK,EAAE;AAEvE,QAAM,WAAW,MAAM,MAAM,KAAK;AAAA,IAChC,QAAQ;AAAA,IACR,SAAS,YAAY,QAAQ,MAAM;AAAA,IACnC,QAAQ,QAAQ;AAAA,EAClB,CAAC;AAED,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,MAAM,WAAW,QAAQ;AAAA,EACjC;AAEA,QAAM,OAAQ,MAAM,SAAS,KAAK;AAelC,QAAM,SACJ,KAAK,gCAAgC,aACrC,KAAK,gCAAgC,WACjC,KAAK,8BACL;AACN,QAAM,UAAU,CAAC,MACf,OAAO,MAAM,YAAY,OAAO,SAAS,CAAC,IACtC,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,IACxC;AACN,SAAO;AAAA,IACL,qBAAqB,KAAK,2BAA2B;AAAA,IACrD,YAAY,KAAK,gBAAgB;AAAA,IACjC,UAAU,KAAK,cAAc;AAAA,IAC7B,gBAAgB,KAAK,qBAAqB;AAAA,IAC1C,UACE,OAAO,KAAK,cAAc,YAAY,KAAK,UAAU,SAAS,IAC1D,KAAK,YACL;AAAA,IACN,mBACE,OAAO,KAAK,wBAAwB,YACpC,KAAK,oBAAoB,SAAS,IAC9B,KAAK,sBACL;AAAA,IACN,uBACE,OAAO,KAAK,4BAA4B,YACxC,KAAK,wBAAwB,SAAS,IAClC,KAAK,0BACL;AAAA,IACN,mBACE,OAAO,KAAK,wBAAwB,YACpC,KAAK,oBAAoB,SAAS,IAC9B,KAAK,sBACL;AAAA,IACN,kBACE,OAAO,KAAK,uBAAuB,YACnC,KAAK,mBAAmB,SAAS,IAC7B,KAAK,qBACL;AAAA,IACN,uBACE,OAAO,KAAK,6BAA6B,YACzC,KAAK,yBAAyB,SAAS,IACnC,KAAK,2BACL;AAAA,IACN,0BAA0B;AAAA,IAC1B,6BAA6B,QAAQ,KAAK,+BAA+B;AAAA,IACzE,6BAA6B,QAAQ,KAAK,+BAA+B;AAAA,EAC3E;AACF;AAGO,IAAM,wBAA8C;AAAA,EACzD,qBAAqB;AAAA,EACrB,YAAY;AAAA,EACZ,UAAU;AAAA,EACV,gBAAgB;AAAA,EAChB,UAAU;AAAA,EACV,mBAAmB;AAAA,EACnB,uBAAuB;AAAA,EACvB,mBAAmB;AAAA,EACnB,kBAAkB;AAAA,EAClB,uBAAuB;AAAA,EACvB,0BAA0B;AAAA,EAC1B,6BAA6B;AAAA,EAC7B,6BAA6B;AAC/B;AAuBO,SAAS,mBACd,MACA,UACA,UACqB;AACrB,QAAM,UACJ,KAAK,YAAY,OAAO,KAAK,aAAa,WACpC,KAAK,SAAqC,UAG5C;AACN,QAAM,KAAK,CAAC,QACV,WAAW,OAAO,QAAQ,GAAG,MAAM,YAC9B,QAAQ,GAAG,IACZ;AACN,SAAO;AAAA,IACL,qBACE,UAAU,uBAAuB,GAAG,KAAK,KAAK,SAAS;AAAA,IACzD,YAAY,GAAG,QAAQ,KAAK,SAAS;AAAA,IACrC,UAAU,GAAG,MAAM,KAAK,SAAS;AAAA,IACjC,gBAAgB,SAAS;AAAA,IACzB,UAAU,UAAU,wBAAwB,SAAS,YAAY;AAAA,IACjE,mBAAmB,SAAS,qBAAqB;AAAA,IACjD,uBAAuB,SAAS;AAAA,IAChC,mBAAmB,SAAS;AAAA,IAC5B,kBAAkB,SAAS;AAAA,IAC3B,uBAAuB,SAAS;AAAA,IAChC,0BAA0B,SAAS;AAAA,IACnC,6BAA6B,SAAS;AAAA,IACtC,6BAA6B,SAAS;AAAA,EACxC;AACF;AAEO,IAAM,cAAN,cAA0B,MAAM;AAAA,EAC5B;AAAA,EACA;AAAA,EACT,YAAY,SAAiB,QAAgB,MAAc;AACzD,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,OAAO;AAAA,EACd;AACF;AAEA,eAAsB,WAAW,SAAyD;AACxF,EAAAC,cAAa;AACb,QAAM,WAAW,QAAQ,WAAWD,mBAAkB,QAAQ,QAAQ,EAAE;AACxE,QAAM,SAAS,IAAI,gBAAgB;AACnC,MAAI,QAAQ,UAAU,OAAW,QAAO,IAAI,SAAS,OAAO,QAAQ,KAAK,CAAC;AAC1E,MAAI,QAAQ,OAAQ,QAAO,IAAI,UAAU,QAAQ,MAAM;AACvD,MAAI,QAAQ,KAAM,QAAO,IAAI,QAAQ,QAAQ,IAAI;AACjD,MAAI,QAAQ,OAAQ,QAAO,IAAI,WAAW,QAAQ,MAAM;AACxD,QAAM,KAAK,OAAO,SAAS;AAC3B,QAAM,MAAM,GAAG,OAAO,uBAAuB,KAAK,IAAI,EAAE,KAAK,EAAE;AAE/D,QAAM,WAAW,MAAM,MAAM,KAAK;AAAA,IAChC,QAAQ;AAAA,IACR,SAAS,YAAY,QAAQ,MAAM;AAAA,IACnC,QAAQ,QAAQ;AAAA,EAClB,CAAC;AAED,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,MAAM,WAAW,QAAQ;AAAA,EACjC;AAEA,QAAM,OAAQ,MAAM,SAAS,KAAK;AAKlC,SAAO;AAAA,IACL,OAAO,KAAK,MAAM,IAAI,QAAQ;AAAA,IAC9B,SAAS,KAAK;AAAA,IACd,YAAY,KAAK;AAAA,EACnB;AACF;AAEA,eAAsB,UAAU,SAA2D;AACzF,EAAAC,cAAa;AACb,QAAM,WAAW,QAAQ,WAAWD,mBAAkB,QAAQ,QAAQ,EAAE;AACxE,QAAM,SAAS,IAAI,gBAAgB;AACnC,MAAI,QAAQ,OAAQ,QAAO,IAAI,WAAW,QAAQ,MAAM;AACxD,QAAM,KAAK,OAAO,SAAS;AAC3B,QAAM,MAAM,GAAG,OAAO,wBAAwB,mBAAmB,QAAQ,QAAQ,CAAC,GAAG,KAAK,IAAI,EAAE,KAAK,EAAE;AAEvG,QAAM,WAAW,MAAM,MAAM,KAAK;AAAA,IAChC,QAAQ;AAAA,IACR,SAAS,YAAY,QAAQ,MAAM;AAAA,IACnC,QAAQ,QAAQ;AAAA,EAClB,CAAC;AAED,MAAI,SAAS,WAAW,IAAK,QAAO;AACpC,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,MAAM,WAAW,QAAQ;AAAA,EACjC;AAEA,QAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,SAAO,SAAS,IAAI;AACtB;AA6BA,SAAS,SAAS,MAAmC;AACnD,SAAO;AAAA,IACL,IAAI,KAAK;AAAA,IACT,UAAU,KAAK;AAAA,IACf,QAAQ,KAAK;AAAA,IACb,MAAM,KAAK;AAAA,IACX,MAAM,KAAK;AAAA,IACX,OAAO,KAAK;AAAA,IACZ,SAAS,KAAK;AAAA,IACd,iBAAiB,KAAK;AAAA,IACtB,kBAAkB,KAAK,sBAAsB;AAAA,IAC7C,UAAU,KAAK;AAAA,IACf,YAAY,KAAK,eAAe;AAAA,IAChC,gBAAgB,KAAK,oBAAoB;AAAA,IACzC,WAAW,KAAK,cAAc;AAAA,IAC9B,UAAU,KAAK;AAAA,IACf,QAAQ,KAAK;AAAA,IACb,UAAU,KAAK;AAAA,IACf,QAAQ,KAAK,UAAU;AAAA,IACvB,OAAO,KAAK,SAAS,CAAC;AAAA,IACtB,aAAa,KAAK;AAAA,IAClB,WAAW,KAAK;AAAA,IAChB,WAAW,KAAK;AAAA,EAClB;AACF;AAEA,eAAe,WAAW,UAA0C;AAClE,MAAI,OAAO;AACX,MAAI,UAAU,2BAA2B,SAAS,MAAM;AACxD,MAAI;AACF,UAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,QAAI,KAAK,KAAM,QAAO,KAAK;AAC3B,QAAI,KAAK,QAAS,WAAU,KAAK;AAAA,EACnC,QAAQ;AAAA,EAER;AACA,SAAO,IAAI,YAAY,SAAS,SAAS,QAAQ,IAAI;AACvD;AAEA,SAASC,gBAAqB;AAC5B,MAAI,OAAO,WAAW,aAAa;AACjC,UAAM,IAAI;AAAA,MACR;AAAA,IAGF;AAAA,EACF;AACF;","names":["DEFAULT_BASE_URL","assertServer"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roottale/cms-client",
3
- "version": "0.18.0",
3
+ "version": "0.20.0",
4
4
  "type": "module",
5
5
  "description": "RootTale CMS Public API server-side fetch client (Bearer rtlk_cust_* auth). SSR-only — refuses to run in the browser to prevent key leak (ADR-0023 §5.1 #15). Pairs with @roottale/cms-renderer-next / @roottale/cms-renderer-astro.",
6
6
  "main": "./dist/server.js",