@roottale/cms-client 0.65.1 → 0.67.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 +29 -0
- package/dist/server.d.ts +61 -1
- package/dist/server.js +95 -5
- package/dist/server.js.map +1 -1
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# @roottale/cms-client
|
|
2
2
|
|
|
3
|
+
## 0.67.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 30d26ac: 공통 메타데이터 해석기·고유 경로 조회·Next SSR 빌더와 캐시 태그를 추가합니다. 기존 canonical과 강제 robots 정책을 보존하고 공유 이미지 대체 텍스트를 OG·X에 동일하게 적용합니다. 분류의 공유 전용 설정과 메타데이터 조회·수정 MCP 도구, Pro 하향 후 공개 적용 유지 계약을 문서와 예제에 반영합니다. 분류 색인 전환을 선언한 사이트가 공개 분류 응답의 `category_index_activated`를 확인한 뒤에만 분류 검색 제외를 적용하도록 안내합니다.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [30d26ac]
|
|
12
|
+
- @roottale/cms-core@0.67.0
|
|
13
|
+
|
|
14
|
+
## 0.66.0
|
|
15
|
+
|
|
16
|
+
### Minor Changes
|
|
17
|
+
|
|
18
|
+
- a58c5d2: 계층형 분류 모델(`category_tree`)에 `entryCategory: "leaf"`를 추가한다. 하위 분류가 없는
|
|
19
|
+
분류면 단계와 무관하게 글을 붙일 수 있고(`categoryDepth`는 최대 단계), 새로 고를 때는
|
|
20
|
+
`resolveCategoryTreeSelection`으로 하위가 없는지 확인한다. 생략하면 기존처럼
|
|
21
|
+
`categoryDepth` 단계 말단에만 붙는다.
|
|
22
|
+
- 716dc9b: 콘텐츠 모델 presentation에 `listPaths`(글을 목록으로 보여 주는 고정 화면)를 추가합니다.
|
|
23
|
+
`data_only`·`detail`·`category_tree`에서 쓸 수 있고, 글 변경 웹훅 `paths`에 함께 실립니다.
|
|
24
|
+
상세 주소가 없는 `data_only` 모델은 이 화면만 갱신하고, ROOT-ADMIN은 그 화면에서 현재
|
|
25
|
+
제목으로 공개 반영을 확인합니다. 공개 콘텐츠 모델 API 응답에도 `listPaths`가 포함됩니다.
|
|
26
|
+
- bb7b516: 검색엔진 소유 확인 코드를 공개 테마 응답 `siteVerification`으로 내보냅니다.
|
|
27
|
+
고객이 ROOT-ADMIN 설정 → 연동에서 Google Search Console·네이버 서치어드바이저 코드를
|
|
28
|
+
붙여 넣으면 `theme.updated` 웹훅으로 바로 반영되고, 사이트는
|
|
29
|
+
`siteVerificationMetadata(theme.siteVerification)`를 루트 metadata `verification`에 넣어
|
|
30
|
+
메타태그로 출력합니다.
|
|
31
|
+
|
|
3
32
|
## 0.65.1
|
|
4
33
|
|
|
5
34
|
## 0.65.0
|
package/dist/server.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { InquiryFormDefinition, InquiryAnswer } from '@roottale/inquiry-forms';
|
|
|
2
2
|
export { InquiryAnswer, InquiryFormDefinition, InquiryFormField, InquiryKind } from '@roottale/inquiry-forms';
|
|
3
3
|
import { InquiryAttribution } from './attribution.js';
|
|
4
4
|
export { parseAttributionJson } from './attribution.js';
|
|
5
|
+
import { PublicRouteMetadata } from '@roottale/cms-core';
|
|
6
|
+
export { MetadataDefaultRules, MetadataDefaultRulesSchema, MetadataDefaultsDeclaration, MetadataDefaultsInput, MetadataField, MetadataPatch, MetadataResolutionInput, MetadataRobotsPolicy, MetadataTextRule, MetadataValues, PublicRouteMetadata, formatMetadataDefaults, metadataContentImageUrl, readMetadataValues, resolveMetadata } from '@roottale/cms-core';
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* `@roottale/cms-client/server` — RootTale CMS Public API server-side fetch client.
|
|
@@ -312,6 +314,16 @@ interface RootTaleTheme {
|
|
|
312
314
|
actionDock?: ActionDockSettings;
|
|
313
315
|
inquiryBoard?: InquiryBoardSettings;
|
|
314
316
|
siteNav?: SiteNavSettings;
|
|
317
|
+
/**
|
|
318
|
+
* ROOT-ADMIN 연동 설정에 입력한 검색엔진 소유 확인 코드. `siteVerificationMetadata`
|
|
319
|
+
* 로 Next metadata `verification` 모양으로 바꿔 루트 layout 에 출력한다.
|
|
320
|
+
*/
|
|
321
|
+
siteVerification?: SiteVerificationSettings;
|
|
322
|
+
}
|
|
323
|
+
/** 엔진별 소유 확인 코드(고객·RootTale 계정이 각각 확인할 수 있어 여러 개). */
|
|
324
|
+
interface SiteVerificationSettings {
|
|
325
|
+
google?: string[];
|
|
326
|
+
naver?: string[];
|
|
315
327
|
}
|
|
316
328
|
type AnnouncementVariant = "single" | "rotating" | "cta";
|
|
317
329
|
type AnnouncementTone = "default" | "primary";
|
|
@@ -613,6 +625,7 @@ interface CmsContentModel {
|
|
|
613
625
|
source: "code" | "site";
|
|
614
626
|
presentation: {
|
|
615
627
|
readonly kind: "data_only";
|
|
628
|
+
readonly listPaths?: readonly string[];
|
|
616
629
|
} | {
|
|
617
630
|
readonly kind: "fixed_page";
|
|
618
631
|
readonly path: string;
|
|
@@ -622,11 +635,16 @@ interface CmsContentModel {
|
|
|
622
635
|
readonly kind: "detail";
|
|
623
636
|
readonly detailPath: string;
|
|
624
637
|
readonly templateKey?: string;
|
|
638
|
+
/** 글을 목록으로 보여 주는 고정 화면(홈·회사소개 등). 글 변경 웹훅에 함께 실린다. */
|
|
639
|
+
readonly listPaths?: readonly string[];
|
|
625
640
|
} | {
|
|
626
641
|
readonly kind: "category_tree";
|
|
627
642
|
readonly basePath: string;
|
|
628
643
|
readonly categoryDepth: number;
|
|
644
|
+
/** `leaf`면 하위 분류가 없는 분류에 단계와 무관하게 글을 붙인다. 생략은 `deepest`. */
|
|
645
|
+
readonly entryCategory?: "deepest" | "leaf";
|
|
629
646
|
readonly templateKey?: string;
|
|
647
|
+
readonly listPaths?: readonly string[];
|
|
630
648
|
};
|
|
631
649
|
}
|
|
632
650
|
interface FetchContentModelsOptions {
|
|
@@ -738,6 +756,10 @@ interface CmsCategoryCount {
|
|
|
738
756
|
description: string | null;
|
|
739
757
|
seoTitle: string | null;
|
|
740
758
|
seoDescription: string | null;
|
|
759
|
+
/** 공유 전용 설정. 본문 분류 이미지와 별도로 관리한다. */
|
|
760
|
+
seoOgImage?: string | null;
|
|
761
|
+
seoOgImageAlt?: string | null;
|
|
762
|
+
seoNofollow?: boolean | null;
|
|
741
763
|
imageMediaId?: string | null;
|
|
742
764
|
imageUrl?: string | null;
|
|
743
765
|
imageAlt?: string | null;
|
|
@@ -1329,6 +1351,34 @@ declare function sitePatternPresentationAttributes(presentation: CmsSitePatternP
|
|
|
1329
1351
|
*/
|
|
1330
1352
|
declare function selectSitePatternForSlot(patternSlots: Readonly<Record<string, string | null>> | null | undefined, patterns: readonly CmsSitePattern[], slot?: string): CmsSitePattern | null;
|
|
1331
1353
|
|
|
1354
|
+
declare const METADATA_CACHE_TAG = "rt-metadata";
|
|
1355
|
+
declare const SITE_METADATA_DEFAULTS_CACHE_TAG = "rt-metadata-defaults";
|
|
1356
|
+
/** locale를 생략한 요청은 사이트 기본 locale을 조회한다. */
|
|
1357
|
+
declare function routeMetadataCacheTag(routeKey: string, locale?: string): string;
|
|
1358
|
+
interface FetchRouteMetadataOptions {
|
|
1359
|
+
apiKey: string;
|
|
1360
|
+
baseUrl?: string;
|
|
1361
|
+
siteId?: string;
|
|
1362
|
+
routeKey: string;
|
|
1363
|
+
locale?: string;
|
|
1364
|
+
signal?: AbortSignal;
|
|
1365
|
+
/** 기본 300초. 웹훅 유실 시 재조회하며, 0이면 항상 최신값을 읽는다. */
|
|
1366
|
+
revalidate?: number;
|
|
1367
|
+
}
|
|
1368
|
+
/**
|
|
1369
|
+
* 고유 route key 직접 조회. 등록되지 않은 경로만 null이며, 연결된 빈 설정은
|
|
1370
|
+
* overrides:{}인 객체다. HTTP/네트워크/응답 오류는 throw하여 빈 설정으로 캐시하지 않는다.
|
|
1371
|
+
* 공개 읽기는 구독 등급을 검사하지 않아 요금제 하향 후에도 기존 설정을 유지한다.
|
|
1372
|
+
*/
|
|
1373
|
+
declare function fetchRouteMetadata(options: FetchRouteMetadataOptions): Promise<PublicRouteMetadata | null>;
|
|
1374
|
+
|
|
1375
|
+
/**
|
|
1376
|
+
* 배포 도구가 정규화해 등록한 manifest의 SHA-256(64자리 hex).
|
|
1377
|
+
* 이 함수는 스키마 검사·기본값 채우기를 하지 않는다. 원본 JSON을 그대로 해시하면
|
|
1378
|
+
* 등록 API가 정규화한 계약과 달라질 수 있으므로 배포 시 정규화한 manifest를 사용한다.
|
|
1379
|
+
*/
|
|
1380
|
+
declare function metadataContractChecksum(normalizedContract: unknown): Promise<string>;
|
|
1381
|
+
|
|
1332
1382
|
/**
|
|
1333
1383
|
* 설정류 fetch 캐시 tag 한 벌 (ADR-0096 Phase C).
|
|
1334
1384
|
*
|
|
@@ -1522,6 +1572,16 @@ declare function contentIdMeta(postId: string): string;
|
|
|
1522
1572
|
declare function jsonLdJson(schema: JsonLd | JsonLd[]): string;
|
|
1523
1573
|
/** JSON-LD 객체 → `<script type="application/ld+json">` HTML 문자열 (XSS-safe). */
|
|
1524
1574
|
declare function jsonLdScript(schema: JsonLd | JsonLd[]): string;
|
|
1575
|
+
/** Next.js `Metadata["verification"]` 와 구조 호환. */
|
|
1576
|
+
interface SiteVerificationMetadata {
|
|
1577
|
+
google?: string[];
|
|
1578
|
+
other?: Record<string, string[]>;
|
|
1579
|
+
}
|
|
1580
|
+
/**
|
|
1581
|
+
* `theme.siteVerification` → 루트 layout metadata 의 `verification`.
|
|
1582
|
+
* 코드가 없으면 undefined 를 돌려줘 spread 로 생략할 수 있다.
|
|
1583
|
+
*/
|
|
1584
|
+
declare function siteVerificationMetadata(settings: SiteVerificationSettings | undefined): SiteVerificationMetadata | undefined;
|
|
1525
1585
|
|
|
1526
1586
|
/**
|
|
1527
1587
|
* `@roottale/cms-client` — 리드(문의) 접수 server-side 헬퍼.
|
|
@@ -1631,4 +1691,4 @@ declare function formatKoreanPhoneNumber(input: string): string;
|
|
|
1631
1691
|
*/
|
|
1632
1692
|
declare function submitInquiry(options: SubmitInquiryOptions): Promise<SubmitInquiryResult>;
|
|
1633
1693
|
|
|
1634
|
-
export { type ActionDockAction, type ActionDockKind, type ActionDockPosition, type ActionDockSettings, type AnalyticsTagConfig, type AnalyticsTagProvider, type AnnouncementItem, type AnnouncementSettings, type AnnouncementTone, type AnnouncementVariant, type ArticleSchemaInput, BLOG_SETTINGS_CACHE_TAG, BUSINESS_CACHE_TAG, type BusinessDayCode, type BusinessOpeningHours, type BusinessType, CMS_CLIENT_VERSION, COLLECTIONS_CACHE_TAG, CmsApiError, type CmsArchiveRelatedGroup, type CmsArchiveRelatedPost, type CmsCategoryCount, type CmsCategoryCounts, type CmsContentModel, type CmsExposureCampaign, type CmsExposureDecision, type CmsFieldMetaEntry, type CmsPostContent, type CmsPostContentPage, type CmsPostPreviewContent, type CmsPostPreviewInfo, type CmsPostTranslationRef, type CmsPostType, type CmsRouteCollection, type CmsSearchHit, type CmsSitePattern, type CmsSitePatternPresentation, type CmsTaxonomyKind, type CmsTermRef, DEFAULT_BLOG_SETTINGS, DEFAULT_SITE_PATTERN_PRESENTATION, EXPOSURE_CACHE_TAG, type FetchAnalyticsOptions, type FetchAuthorsOptions, type FetchBlogSettingsOptions, type FetchBusinessProfileOptions, type FetchCategoryCountsOptions, type FetchCollectionsOptions, type FetchContentModelsOptions, type FetchExposureDecisionOptions, type FetchExposureOptions, type FetchInquiryFormOptions, type FetchMenuOptions, type FetchMenusOptions, type FetchPageBySlugOptions, type FetchPostOptions, type FetchPostPreviewOptions, type FetchPostsOptions, type FetchRedirectsOptions, type FetchSiteInquiriesOptions, type FetchSiteInquiryOptions, type FetchSitePatternsOptions, type FetchThemeOptions, type FormInquiryReceipt, INQUIRY_ERROR_MESSAGES, InquiryAttribution, type InquiryBoardSettings, type InquiryVertical, LOCAL_BUSINESS_MAX_SERVICES, LOCAL_BUSINESS_SERVICE_MAX_LENGTH, type LocalBusinessProfileInput, MENUS_CACHE_TAG, POST_FOOTER_PATTERN_SLOT, type PublishedInquiryForm, type ResolvedPostDisplay, type RootTaleAuthor, type RootTaleBlogPostCta, type RootTaleBlogSettings, type RootTaleBusinessProfile, type RootTaleMenu, type RootTaleMenuItem, type RootTaleRedirect, type RootTaleSiteProfile, type RootTaleSitemapSettings, type RootTaleTheme, type RssFeedInput, type RssItemInput, SETTINGS_CACHE_TAGS, SITE_PATTERNS_CACHE_TAG, type SearchPostsOptions, type SiteAnalyticsTags, type SiteInquiryDetail, type SiteInquiryListItem, type SiteInquiryListPage, type SiteInquiryStatus, type SiteNavColumn, type SiteNavCta, type SiteNavGroup, type SiteNavLink, type SiteNavSettings, type SubmitFormInquiryFields, type SubmitFormInquiryOptions, type SubmitFormInquiryResult, type SubmitInquiryFields, type SubmitInquiryOptions, type SubmitInquiryResult, type SubmitSiteInquiryFields, type SubmitSiteInquiryOptions, type SubmitSiteInquiryResult, THEME_CACHE_TAG, articleSchema, breadcrumbSchema, businessProfileFromWire, collectionPageSchema, contentIdMeta, faqSchema, fetchAnalyticsConfig, fetchAuthors, fetchBlogSettings, fetchBusinessProfile, fetchCategoryCounts, fetchCollections, fetchCollectionsSafeDefault, fetchContentModels, fetchExposure, fetchExposureDecision, fetchInquiryForm, fetchMenu, fetchMenus, fetchPageBySlug, fetchPost, fetchPostPreview, fetchPosts, fetchRedirects, fetchSiteInquiries, fetchSiteInquiry, fetchSitePatterns, fetchTheme, formatKoreanPhoneNumber, generateRssXml, inquiryErrorMessage, isPreviewExpiredError, jsonLdJson, jsonLdScript, localBusinessSchema, organizationSchema, parseInquiryRedirect, profilePageSchema, resolvePostDisplay, resolveSearchHitPath, rssItemsFromPosts, searchPosts, selectRelatedPosts, selectSitePatternForSlot, sitePatternPresentationAttributes, submitFormInquiry, submitInquiry, submitSiteInquiry, websiteSchema };
|
|
1694
|
+
export { type ActionDockAction, type ActionDockKind, type ActionDockPosition, type ActionDockSettings, type AnalyticsTagConfig, type AnalyticsTagProvider, type AnnouncementItem, type AnnouncementSettings, type AnnouncementTone, type AnnouncementVariant, type ArticleSchemaInput, BLOG_SETTINGS_CACHE_TAG, BUSINESS_CACHE_TAG, type BusinessDayCode, type BusinessOpeningHours, type BusinessType, CMS_CLIENT_VERSION, COLLECTIONS_CACHE_TAG, CmsApiError, type CmsArchiveRelatedGroup, type CmsArchiveRelatedPost, type CmsCategoryCount, type CmsCategoryCounts, type CmsContentModel, type CmsExposureCampaign, type CmsExposureDecision, type CmsFieldMetaEntry, type CmsPostContent, type CmsPostContentPage, type CmsPostPreviewContent, type CmsPostPreviewInfo, type CmsPostTranslationRef, type CmsPostType, type CmsRouteCollection, type CmsSearchHit, type CmsSitePattern, type CmsSitePatternPresentation, type CmsTaxonomyKind, type CmsTermRef, DEFAULT_BLOG_SETTINGS, DEFAULT_SITE_PATTERN_PRESENTATION, EXPOSURE_CACHE_TAG, type FetchAnalyticsOptions, type FetchAuthorsOptions, type FetchBlogSettingsOptions, type FetchBusinessProfileOptions, type FetchCategoryCountsOptions, type FetchCollectionsOptions, type FetchContentModelsOptions, type FetchExposureDecisionOptions, type FetchExposureOptions, type FetchInquiryFormOptions, type FetchMenuOptions, type FetchMenusOptions, type FetchPageBySlugOptions, type FetchPostOptions, type FetchPostPreviewOptions, type FetchPostsOptions, type FetchRedirectsOptions, type FetchRouteMetadataOptions, type FetchSiteInquiriesOptions, type FetchSiteInquiryOptions, type FetchSitePatternsOptions, type FetchThemeOptions, type FormInquiryReceipt, INQUIRY_ERROR_MESSAGES, InquiryAttribution, type InquiryBoardSettings, type InquiryVertical, LOCAL_BUSINESS_MAX_SERVICES, LOCAL_BUSINESS_SERVICE_MAX_LENGTH, type LocalBusinessProfileInput, MENUS_CACHE_TAG, METADATA_CACHE_TAG, POST_FOOTER_PATTERN_SLOT, type PublishedInquiryForm, type ResolvedPostDisplay, type RootTaleAuthor, type RootTaleBlogPostCta, type RootTaleBlogSettings, type RootTaleBusinessProfile, type RootTaleMenu, type RootTaleMenuItem, type RootTaleRedirect, type RootTaleSiteProfile, type RootTaleSitemapSettings, type RootTaleTheme, type RssFeedInput, type RssItemInput, SETTINGS_CACHE_TAGS, SITE_METADATA_DEFAULTS_CACHE_TAG, SITE_PATTERNS_CACHE_TAG, type SearchPostsOptions, type SiteAnalyticsTags, type SiteInquiryDetail, type SiteInquiryListItem, type SiteInquiryListPage, type SiteInquiryStatus, type SiteNavColumn, type SiteNavCta, type SiteNavGroup, type SiteNavLink, type SiteNavSettings, type SiteVerificationMetadata, type SiteVerificationSettings, type SubmitFormInquiryFields, type SubmitFormInquiryOptions, type SubmitFormInquiryResult, type SubmitInquiryFields, type SubmitInquiryOptions, type SubmitInquiryResult, type SubmitSiteInquiryFields, type SubmitSiteInquiryOptions, type SubmitSiteInquiryResult, THEME_CACHE_TAG, articleSchema, breadcrumbSchema, businessProfileFromWire, collectionPageSchema, contentIdMeta, faqSchema, fetchAnalyticsConfig, fetchAuthors, fetchBlogSettings, fetchBusinessProfile, fetchCategoryCounts, fetchCollections, fetchCollectionsSafeDefault, fetchContentModels, fetchExposure, fetchExposureDecision, fetchInquiryForm, fetchMenu, fetchMenus, fetchPageBySlug, fetchPost, fetchPostPreview, fetchPosts, fetchRedirects, fetchRouteMetadata, fetchSiteInquiries, fetchSiteInquiry, fetchSitePatterns, fetchTheme, formatKoreanPhoneNumber, generateRssXml, inquiryErrorMessage, isPreviewExpiredError, jsonLdJson, jsonLdScript, localBusinessSchema, metadataContractChecksum, organizationSchema, parseInquiryRedirect, profilePageSchema, resolvePostDisplay, resolveSearchHitPath, routeMetadataCacheTag, rssItemsFromPosts, searchPosts, selectRelatedPosts, selectSitePatternForSlot, sitePatternPresentationAttributes, siteVerificationMetadata, submitFormInquiry, submitInquiry, submitSiteInquiry, websiteSchema };
|
package/dist/server.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
|
|
5
5
|
// src/server-base.ts
|
|
6
6
|
var DEFAULT_BASE_URL = "https://api.roottale.com";
|
|
7
|
-
var CLIENT_VERSION = true ? "0.
|
|
7
|
+
var CLIENT_VERSION = true ? "0.67.0" : "dev";
|
|
8
8
|
var CMS_CLIENT_VERSION = CLIENT_VERSION;
|
|
9
9
|
function authHeaders(apiKey) {
|
|
10
10
|
return {
|
|
@@ -173,6 +173,7 @@ async function fetchTheme(options) {
|
|
|
173
173
|
if (json.actionDock) theme.actionDock = json.actionDock;
|
|
174
174
|
if (json.inquiryBoard) theme.inquiryBoard = json.inquiryBoard;
|
|
175
175
|
if (json.siteNav) theme.siteNav = json.siteNav;
|
|
176
|
+
if (json.siteVerification) theme.siteVerification = json.siteVerification;
|
|
176
177
|
return theme;
|
|
177
178
|
}
|
|
178
179
|
async function fetchAnalyticsConfig(options) {
|
|
@@ -373,6 +374,10 @@ async function fetchContentModels(options) {
|
|
|
373
374
|
const cardinality = model.cardinality === "singleton" ? "singleton" : "collection";
|
|
374
375
|
const preset = model.preset === "page" || model.preset === "article" || model.preset === "entity" ? model.preset : null;
|
|
375
376
|
const rawPresentation = model.presentation && typeof model.presentation === "object" && !Array.isArray(model.presentation) ? model.presentation : {};
|
|
377
|
+
const listPaths = Array.isArray(rawPresentation.listPaths) ? rawPresentation.listPaths.filter(
|
|
378
|
+
(item) => typeof item === "string" && item.startsWith("/")
|
|
379
|
+
) : [];
|
|
380
|
+
const withListPaths = listPaths.length > 0 ? { listPaths } : {};
|
|
376
381
|
const presentation = rawPresentation.kind === "fixed_page" && typeof rawPresentation.path === "string" ? {
|
|
377
382
|
kind: "fixed_page",
|
|
378
383
|
path: rawPresentation.path,
|
|
@@ -383,13 +388,16 @@ async function fetchContentModels(options) {
|
|
|
383
388
|
} : rawPresentation.kind === "detail" && typeof rawPresentation.detailPath === "string" ? {
|
|
384
389
|
kind: "detail",
|
|
385
390
|
detailPath: rawPresentation.detailPath,
|
|
386
|
-
...typeof rawPresentation.templateKey === "string" ? { templateKey: rawPresentation.templateKey } : {}
|
|
391
|
+
...typeof rawPresentation.templateKey === "string" ? { templateKey: rawPresentation.templateKey } : {},
|
|
392
|
+
...withListPaths
|
|
387
393
|
} : rawPresentation.kind === "category_tree" && typeof rawPresentation.basePath === "string" && typeof rawPresentation.categoryDepth === "number" ? {
|
|
388
394
|
kind: "category_tree",
|
|
389
395
|
basePath: rawPresentation.basePath,
|
|
390
396
|
categoryDepth: rawPresentation.categoryDepth,
|
|
391
|
-
...
|
|
392
|
-
|
|
397
|
+
...rawPresentation.entryCategory === "leaf" || rawPresentation.entryCategory === "deepest" ? { entryCategory: rawPresentation.entryCategory } : {},
|
|
398
|
+
...typeof rawPresentation.templateKey === "string" ? { templateKey: rawPresentation.templateKey } : {},
|
|
399
|
+
...withListPaths
|
|
400
|
+
} : { kind: "data_only", ...withListPaths };
|
|
393
401
|
return [{
|
|
394
402
|
key: model.key,
|
|
395
403
|
label: model.label,
|
|
@@ -551,6 +559,9 @@ async function fetchCategoryCounts(options) {
|
|
|
551
559
|
description: typeof o.description === "string" ? o.description : null,
|
|
552
560
|
seoTitle: typeof o.seo_title === "string" ? o.seo_title : null,
|
|
553
561
|
seoDescription: typeof o.seo_description === "string" ? o.seo_description : null,
|
|
562
|
+
seoOgImage: typeof o.seo_og_image === "string" ? o.seo_og_image : null,
|
|
563
|
+
seoOgImageAlt: typeof o.seo_og_image_alt === "string" ? o.seo_og_image_alt : null,
|
|
564
|
+
seoNofollow: typeof o.seo_nofollow === "boolean" ? o.seo_nofollow : null,
|
|
554
565
|
imageMediaId: typeof o.image_media_id === "string" ? o.image_media_id : null,
|
|
555
566
|
imageUrl: typeof o.image_url === "string" ? o.image_url : null,
|
|
556
567
|
imageAlt: typeof o.image_alt === "string" ? o.image_alt : null,
|
|
@@ -1188,6 +1199,63 @@ function selectSitePatternForSlot(patternSlots, patterns, slot = POST_FOOTER_PAT
|
|
|
1188
1199
|
return patterns.find((pattern) => pattern.key === key) ?? null;
|
|
1189
1200
|
}
|
|
1190
1201
|
|
|
1202
|
+
// src/server-metadata.ts
|
|
1203
|
+
import { MetadataRobotsPolicySchema, MetadataValuesSchema } from "@roottale/cms-core";
|
|
1204
|
+
import { readMetadataValues, resolveMetadata, formatMetadataDefaults, metadataContentImageUrl, MetadataDefaultRulesSchema } from "@roottale/cms-core";
|
|
1205
|
+
var METADATA_CACHE_TAG = "rt-metadata";
|
|
1206
|
+
var SITE_METADATA_DEFAULTS_CACHE_TAG = "rt-metadata-defaults";
|
|
1207
|
+
function routeMetadataCacheTag(routeKey, locale) {
|
|
1208
|
+
if (!/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(routeKey) || routeKey.length > 160) throw new Error("Invalid metadata route key");
|
|
1209
|
+
const language = locale?.trim().toLowerCase() || "default";
|
|
1210
|
+
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(language) || language.length > 40) {
|
|
1211
|
+
throw new Error("Invalid metadata locale");
|
|
1212
|
+
}
|
|
1213
|
+
return `${METADATA_CACHE_TAG}:${routeKey}:${language}`;
|
|
1214
|
+
}
|
|
1215
|
+
async function fetchRouteMetadata(options) {
|
|
1216
|
+
assertServer();
|
|
1217
|
+
const routeTag = routeMetadataCacheTag(options.routeKey, options.locale);
|
|
1218
|
+
const params = new URLSearchParams({ route_key: options.routeKey });
|
|
1219
|
+
if (options.locale) params.set("locale", options.locale);
|
|
1220
|
+
if (options.siteId) params.set("site_id", options.siteId);
|
|
1221
|
+
const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
1222
|
+
const response = await fetch(`${baseUrl}/v1/cms/public/metadata?${params}`, buildGetInit(options.apiKey, {
|
|
1223
|
+
signal: options.signal,
|
|
1224
|
+
revalidate: options.revalidate ?? 300,
|
|
1225
|
+
tags: [METADATA_CACHE_TAG, SITE_METADATA_DEFAULTS_CACHE_TAG, routeTag]
|
|
1226
|
+
}));
|
|
1227
|
+
if (!response.ok) throw await toApiError(response);
|
|
1228
|
+
const json = await response.json();
|
|
1229
|
+
if (!json || typeof json !== "object" || !("metadata" in json)) throw unexpectedResponse(response.status);
|
|
1230
|
+
if (json.metadata === null) return null;
|
|
1231
|
+
if (!isRouteMetadata(json.metadata) || json.metadata.routeKey !== options.routeKey) throw unexpectedResponse(response.status);
|
|
1232
|
+
return json.metadata;
|
|
1233
|
+
}
|
|
1234
|
+
function isValues(value) {
|
|
1235
|
+
return MetadataValuesSchema.safeParse(value).success;
|
|
1236
|
+
}
|
|
1237
|
+
function isRouteMetadata(value) {
|
|
1238
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
1239
|
+
const row = value;
|
|
1240
|
+
return typeof row.routeKey === "string" && typeof row.locale === "string" && typeof row.path === "string" && row.path.startsWith("/") && !row.path.startsWith("//") && typeof row.version === "string" && isValues(row.overrides) && isValues(row.defaults) && isValues(row.effective) && (row.fallback === void 0 || isValues(row.fallback)) && (row.robots === void 0 || MetadataRobotsPolicySchema.safeParse(row.robots).success) && Array.isArray(row.supportedFields) && row.supportedFields.every((field) => ["title", "description", "ogImage", "ogImageAlt", "noindex", "nofollow"].includes(field));
|
|
1241
|
+
}
|
|
1242
|
+
function unexpectedResponse(status) {
|
|
1243
|
+
return new CmsApiError("RootTale CMS API returned invalid route metadata", status, "unexpected_response");
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
// src/metadata-contract-checksum.ts
|
|
1247
|
+
function canonicalize(value) {
|
|
1248
|
+
if (Array.isArray(value)) return value.map(canonicalize);
|
|
1249
|
+
if (!value || typeof value !== "object") return value;
|
|
1250
|
+
return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, child]) => [key, canonicalize(child)]));
|
|
1251
|
+
}
|
|
1252
|
+
async function metadataContractChecksum(normalizedContract) {
|
|
1253
|
+
const json = JSON.stringify(canonicalize(normalizedContract));
|
|
1254
|
+
if (json === void 0) throw new Error("Metadata contract must be a normalized JSON value");
|
|
1255
|
+
const digest = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(json));
|
|
1256
|
+
return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1191
1259
|
// src/server-cache-tags.ts
|
|
1192
1260
|
var SETTINGS_CACHE_TAGS = [
|
|
1193
1261
|
THEME_CACHE_TAG,
|
|
@@ -1196,7 +1264,9 @@ var SETTINGS_CACHE_TAGS = [
|
|
|
1196
1264
|
BLOG_SETTINGS_CACHE_TAG,
|
|
1197
1265
|
COLLECTIONS_CACHE_TAG,
|
|
1198
1266
|
EXPOSURE_CACHE_TAG,
|
|
1199
|
-
SITE_PATTERNS_CACHE_TAG
|
|
1267
|
+
SITE_PATTERNS_CACHE_TAG,
|
|
1268
|
+
METADATA_CACHE_TAG,
|
|
1269
|
+
SITE_METADATA_DEFAULTS_CACHE_TAG
|
|
1200
1270
|
];
|
|
1201
1271
|
|
|
1202
1272
|
// src/seo.ts
|
|
@@ -1477,6 +1547,15 @@ function escapeXml(value) {
|
|
|
1477
1547
|
function cdata(value) {
|
|
1478
1548
|
return `<![CDATA[${value.replace(/]]>/g, "]]]]><![CDATA[>")}]]>`;
|
|
1479
1549
|
}
|
|
1550
|
+
function siteVerificationMetadata(settings) {
|
|
1551
|
+
const google = settings?.google?.filter(Boolean) ?? [];
|
|
1552
|
+
const naver = settings?.naver?.filter(Boolean) ?? [];
|
|
1553
|
+
if (google.length === 0 && naver.length === 0) return void 0;
|
|
1554
|
+
return {
|
|
1555
|
+
...google.length > 0 ? { google } : {},
|
|
1556
|
+
...naver.length > 0 ? { other: { "naver-site-verification": naver } } : {}
|
|
1557
|
+
};
|
|
1558
|
+
}
|
|
1480
1559
|
|
|
1481
1560
|
// src/inquiry.ts
|
|
1482
1561
|
var DEFAULT_BASE_URL2 = "https://api.roottale.com";
|
|
@@ -1634,8 +1713,11 @@ export {
|
|
|
1634
1713
|
LOCAL_BUSINESS_MAX_SERVICES,
|
|
1635
1714
|
LOCAL_BUSINESS_SERVICE_MAX_LENGTH,
|
|
1636
1715
|
MENUS_CACHE_TAG,
|
|
1716
|
+
METADATA_CACHE_TAG,
|
|
1717
|
+
MetadataDefaultRulesSchema,
|
|
1637
1718
|
POST_FOOTER_PATTERN_SLOT,
|
|
1638
1719
|
SETTINGS_CACHE_TAGS,
|
|
1720
|
+
SITE_METADATA_DEFAULTS_CACHE_TAG,
|
|
1639
1721
|
SITE_PATTERNS_CACHE_TAG,
|
|
1640
1722
|
THEME_CACHE_TAG,
|
|
1641
1723
|
articleSchema,
|
|
@@ -1662,28 +1744,36 @@ export {
|
|
|
1662
1744
|
fetchPostPreview,
|
|
1663
1745
|
fetchPosts,
|
|
1664
1746
|
fetchRedirects,
|
|
1747
|
+
fetchRouteMetadata,
|
|
1665
1748
|
fetchSiteInquiries,
|
|
1666
1749
|
fetchSiteInquiry,
|
|
1667
1750
|
fetchSitePatterns,
|
|
1668
1751
|
fetchTheme,
|
|
1669
1752
|
formatKoreanPhoneNumber,
|
|
1753
|
+
formatMetadataDefaults,
|
|
1670
1754
|
generateRssXml,
|
|
1671
1755
|
inquiryErrorMessage,
|
|
1672
1756
|
isPreviewExpiredError,
|
|
1673
1757
|
jsonLdJson,
|
|
1674
1758
|
jsonLdScript,
|
|
1675
1759
|
localBusinessSchema,
|
|
1760
|
+
metadataContentImageUrl,
|
|
1761
|
+
metadataContractChecksum,
|
|
1676
1762
|
organizationSchema,
|
|
1677
1763
|
parseAttributionJson,
|
|
1678
1764
|
parseInquiryRedirect,
|
|
1679
1765
|
profilePageSchema,
|
|
1766
|
+
readMetadataValues,
|
|
1767
|
+
resolveMetadata,
|
|
1680
1768
|
resolvePostDisplay,
|
|
1681
1769
|
resolveSearchHitPath,
|
|
1770
|
+
routeMetadataCacheTag,
|
|
1682
1771
|
rssItemsFromPosts,
|
|
1683
1772
|
searchPosts,
|
|
1684
1773
|
selectRelatedPosts,
|
|
1685
1774
|
selectSitePatternForSlot,
|
|
1686
1775
|
sitePatternPresentationAttributes,
|
|
1776
|
+
siteVerificationMetadata,
|
|
1687
1777
|
submitFormInquiry,
|
|
1688
1778
|
submitInquiry,
|
|
1689
1779
|
submitSiteInquiry,
|