@roottale/cms-core 0.67.0 → 0.68.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 +10 -0
- package/dist/index.cjs +106 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +538 -32
- package/dist/index.d.ts +538 -32
- package/dist/index.js +105 -35
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @roottale/cms-core
|
|
2
2
|
|
|
3
|
+
## 0.68.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- d7130d8: 기존 고객 사이트의 검색·공유 기본값을 선언형 계약으로 보존합니다. 문자열 커스텀 필드 템플릿, 본문 문단 대체값, Unicode와 단어 경계 말줄임, 고정 이미지 기본값, 모델별 강제 robots와 분류 공개 경로를 지원합니다. 수동 SEO 설정은 우선하며 강제 검색 차단은 유지합니다.
|
|
8
|
+
|
|
9
|
+
분류 조회 SDK에 색인 전환 상태와 추가 캐시 태그를 연결하고 외부 연동 문서·실행 예제를 함께 제공합니다.
|
|
10
|
+
|
|
11
|
+
## 0.67.1
|
|
12
|
+
|
|
3
13
|
## 0.67.0
|
|
4
14
|
|
|
5
15
|
### Minor Changes
|
package/dist/index.cjs
CHANGED
|
@@ -98,6 +98,7 @@ __export(src_exports, {
|
|
|
98
98
|
isValidSitePatternKey: () => isValidSitePatternKey,
|
|
99
99
|
issuePreviewToken: () => issuePreviewToken,
|
|
100
100
|
mergeMetadataPatch: () => mergeMetadataPatch,
|
|
101
|
+
metadataBodyParagraphs: () => metadataBodyParagraphs,
|
|
101
102
|
metadataContentImageUrl: () => metadataContentImageUrl,
|
|
102
103
|
normalizeEmbed: () => normalizeEmbed,
|
|
103
104
|
normalizeHexColor: () => normalizeHexColor,
|
|
@@ -2102,42 +2103,37 @@ function mergeMetadataPatch(current, input) {
|
|
|
2102
2103
|
}
|
|
2103
2104
|
|
|
2104
2105
|
// src/metadata-defaults.ts
|
|
2106
|
+
var import_zod4 = require("zod");
|
|
2107
|
+
|
|
2108
|
+
// src/metadata-text.ts
|
|
2105
2109
|
var import_zod3 = require("zod");
|
|
2106
2110
|
var fallbackText = import_zod3.z.string().max(2e3);
|
|
2107
|
-
var
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
return false;
|
|
2113
|
-
}
|
|
2114
|
-
}, "\uC774\uBBF8\uC9C0 \uD5C8\uC6A9 \uBAA9\uB85D\uC5D0\uB294 \uACBD\uB85C \uC5C6\uB294 HTTPS origin\uB9CC \uC120\uC5B8\uD569\uB2C8\uB2E4");
|
|
2111
|
+
var templateToken = /\{\{(?:title|field:[a-zA-Z][a-zA-Z0-9_-]{0,159})\}\}/gu;
|
|
2112
|
+
var template = fallbackText.refine(
|
|
2113
|
+
(value) => !/[{}]/u.test(value.replace(templateToken, "")),
|
|
2114
|
+
"\uC790\uB3D9 \uD14D\uC2A4\uD2B8\uC5D0\uB294 {{title}} \uB610\uB294 {{field:\uD544\uB4DC\uC774\uB984}} \uD1A0\uD070\uB9CC \uC0AC\uC6A9\uD569\uB2C8\uB2E4"
|
|
2115
|
+
);
|
|
2115
2116
|
var MetadataTextRuleSchema = import_zod3.z.object({
|
|
2116
|
-
source: import_zod3.z.enum(["content", "fallback"]).optional(),
|
|
2117
|
+
source: import_zod3.z.enum(["content", "fallback", "template"]).optional(),
|
|
2118
|
+
template: template.optional(),
|
|
2117
2119
|
stripLeadingLabel: import_zod3.z.enum(["question", "answer"]).optional(),
|
|
2118
2120
|
replaceLineBreakTokens: import_zod3.z.boolean().optional(),
|
|
2119
2121
|
removeEmDashes: import_zod3.z.boolean().optional(),
|
|
2120
2122
|
normalizeWhitespace: import_zod3.z.boolean().optional(),
|
|
2121
2123
|
maxLength: import_zod3.z.number().int().min(1).max(2e3).optional(),
|
|
2124
|
+
lengthUnit: import_zod3.z.enum(["code-units", "code-points"]).optional(),
|
|
2125
|
+
wordBoundaryMinLength: import_zod3.z.number().int().min(1).max(2e3).optional(),
|
|
2122
2126
|
ellipsis: import_zod3.z.string().max(10).optional(),
|
|
2127
|
+
bodyFallback: import_zod3.z.object({
|
|
2128
|
+
preferContaining: import_zod3.z.string().trim().min(1).max(100).optional(),
|
|
2129
|
+
stripLeadingLabel: import_zod3.z.literal("conclusion").optional()
|
|
2130
|
+
}).strict().optional(),
|
|
2123
2131
|
fallback: fallbackText.optional(),
|
|
2124
2132
|
fallbackWithParent: fallbackText.optional(),
|
|
2125
2133
|
fallbackWithoutParent: fallbackText.optional(),
|
|
2126
2134
|
fallbackBySlug: import_zod3.z.record(import_zod3.z.string().max(200), fallbackText).optional()
|
|
2127
|
-
}).strict().refine((rule) => !rule.ellipsis || rule.maxLength !== void 0 && rule.ellipsis.length < rule.maxLength, "\uB9D0\uC904\uC784\uD45C\uC5D0\uB294 \uB354 \uAE34 \uCD5C\uB300 \uAE38\uC774\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4");
|
|
2128
|
-
|
|
2129
|
-
title: MetadataTextRuleSchema.optional(),
|
|
2130
|
-
description: MetadataTextRuleSchema.optional(),
|
|
2131
|
-
useFirstBodyImage: import_zod3.z.boolean().optional(),
|
|
2132
|
-
useTermImage: import_zod3.z.boolean().optional(),
|
|
2133
|
-
imageAltSource: import_zod3.z.enum(["content", "title"]).optional(),
|
|
2134
|
-
legacyImageSource: import_zod3.z.literal("legacy-thumbnail").optional(),
|
|
2135
|
-
imageAllowedOrigins: import_zod3.z.array(imageOrigin).min(1).max(32).optional(),
|
|
2136
|
-
legacyImageAllowedOrigins: import_zod3.z.array(imageOrigin).min(1).max(32).optional(),
|
|
2137
|
-
normalizeCloudflareImages: import_zod3.z.boolean().optional(),
|
|
2138
|
-
fallback: MetadataValuesSchema.optional()
|
|
2139
|
-
}).strict().refine((rule) => !rule.legacyImageSource || Boolean(rule.legacyImageAllowedOrigins?.length), "\uC774\uC804 \uC774\uBBF8\uC9C0 \uC6D0\uBCF8\uC5D0\uB294 \uD5C8\uC6A9 origin \uC120\uC5B8\uC774 \uD544\uC694\uD569\uB2C8\uB2E4");
|
|
2140
|
-
function cleanText(value, rule) {
|
|
2135
|
+
}).strict().refine((rule) => !rule.ellipsis || rule.maxLength !== void 0 && rule.ellipsis.length < rule.maxLength, "\uB9D0\uC904\uC784\uD45C\uC5D0\uB294 \uB354 \uAE34 \uCD5C\uB300 \uAE38\uC774\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4").refine((rule) => rule.source === "template" ? rule.template !== void 0 : rule.template === void 0, "\uD14D\uC2A4\uD2B8 template\uC740 source: template\uACFC \uD568\uAED8 \uC120\uC5B8\uD569\uB2C8\uB2E4").refine((rule) => !rule.wordBoundaryMinLength || Boolean(rule.maxLength && rule.wordBoundaryMinLength < rule.maxLength), "\uB2E8\uC5B4 \uACBD\uACC4\uC5D0\uB294 \uB354 \uAE34 \uCD5C\uB300 \uAE38\uC774\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4");
|
|
2136
|
+
function cleanMetadataText(value, rule) {
|
|
2141
2137
|
let text2 = value.trim();
|
|
2142
2138
|
if (rule?.stripLeadingLabel === "question") text2 = text2.replace(/^\s*Q[..:]?\s*/iu, "");
|
|
2143
2139
|
if (rule?.stripLeadingLabel === "answer") text2 = text2.replace(/^\s*A[..:]?\s*/iu, "");
|
|
@@ -2146,20 +2142,90 @@ function cleanText(value, rule) {
|
|
|
2146
2142
|
if (rule?.normalizeWhitespace) text2 = text2.replace(/\s+/gu, " ");
|
|
2147
2143
|
return text2.trim();
|
|
2148
2144
|
}
|
|
2149
|
-
function
|
|
2150
|
-
|
|
2145
|
+
function metadataBodyParagraphs(value) {
|
|
2146
|
+
const paragraphs = [];
|
|
2147
|
+
const textParts = (node) => {
|
|
2148
|
+
if (Array.isArray(node)) return node.flatMap(textParts);
|
|
2149
|
+
if (!node || typeof node !== "object") return [];
|
|
2150
|
+
const item = node;
|
|
2151
|
+
return [...typeof item.text === "string" ? [item.text] : [], ...textParts(item.content)];
|
|
2152
|
+
};
|
|
2153
|
+
const walk = (node) => {
|
|
2154
|
+
if (Array.isArray(node)) {
|
|
2155
|
+
node.forEach(walk);
|
|
2156
|
+
return;
|
|
2157
|
+
}
|
|
2158
|
+
if (!node || typeof node !== "object") return;
|
|
2159
|
+
const item = node;
|
|
2160
|
+
if (item.type === "paragraph") {
|
|
2161
|
+
const text2 = textParts(item.content).join(" ").replace(/\s+/gu, " ").trim();
|
|
2162
|
+
if (text2) paragraphs.push(text2);
|
|
2163
|
+
} else walk(item.content);
|
|
2164
|
+
};
|
|
2165
|
+
walk(value);
|
|
2166
|
+
return paragraphs;
|
|
2167
|
+
}
|
|
2168
|
+
function formatMetadataText(value, rule, input, name) {
|
|
2169
|
+
let text2 = rule?.source === "fallback" ? "" : cleanMetadataText(value ?? "", rule);
|
|
2170
|
+
if (rule?.source === "template") {
|
|
2171
|
+
text2 = cleanMetadataText((rule.template ?? "").replace(templateToken, (token) => {
|
|
2172
|
+
if (token === "{{title}}") return name;
|
|
2173
|
+
const field = token.slice(8, -2);
|
|
2174
|
+
const value2 = Object.hasOwn(input.fields ?? {}, field) ? input.fields?.[field] : void 0;
|
|
2175
|
+
return typeof value2 === "string" ? value2.trim() : "";
|
|
2176
|
+
}), rule);
|
|
2177
|
+
}
|
|
2178
|
+
if (!text2 && rule?.bodyFallback) {
|
|
2179
|
+
const paragraphs = input.bodyParagraphs ?? [];
|
|
2180
|
+
const preferred = rule.bodyFallback.preferContaining;
|
|
2181
|
+
text2 = (preferred ? paragraphs.find((paragraph) => paragraph.includes(preferred)) : void 0) ?? paragraphs[0] ?? "";
|
|
2182
|
+
if (rule.bodyFallback.stripLeadingLabel === "conclusion") text2 = text2.replace(/^(먼저\s*)?결론부터\s*(말씀드리면|말씀드릴게요)[.!?]?\s*/u, "");
|
|
2183
|
+
text2 = cleanMetadataText(text2, rule);
|
|
2184
|
+
}
|
|
2151
2185
|
if (!text2) {
|
|
2152
|
-
const
|
|
2153
|
-
|
|
2186
|
+
const slugFallback = input.slug && Object.hasOwn(rule?.fallbackBySlug ?? {}, input.slug) ? rule?.fallbackBySlug?.[input.slug] : void 0;
|
|
2187
|
+
const fallback = input.parentId ? rule?.fallbackWithParent ?? rule?.fallback : slugFallback ?? rule?.fallbackWithoutParent ?? rule?.fallback;
|
|
2188
|
+
text2 = cleanMetadataText((fallback ?? "").replaceAll("%s", () => name), rule);
|
|
2154
2189
|
}
|
|
2155
2190
|
if (!text2) return void 0;
|
|
2156
|
-
|
|
2191
|
+
const units = rule?.lengthUnit === "code-points" ? [...text2] : text2.split("");
|
|
2192
|
+
if (rule?.maxLength && units.length > rule.maxLength) {
|
|
2157
2193
|
const suffix = rule.ellipsis ?? "";
|
|
2158
|
-
const
|
|
2194
|
+
const suffixLength = rule.lengthUnit === "code-points" ? [...suffix].length : suffix.length;
|
|
2195
|
+
let head = units.slice(0, rule.maxLength - suffixLength).join("");
|
|
2196
|
+
const boundary = head.lastIndexOf(" ");
|
|
2197
|
+
if (rule.wordBoundaryMinLength && boundary >= rule.wordBoundaryMinLength) head = head.slice(0, boundary);
|
|
2159
2198
|
return suffix ? head.trimEnd() + suffix : head;
|
|
2160
2199
|
}
|
|
2161
2200
|
return text2;
|
|
2162
2201
|
}
|
|
2202
|
+
|
|
2203
|
+
// src/metadata-defaults.ts
|
|
2204
|
+
var imageOrigin = import_zod4.z.string().max(2048).refine((value) => {
|
|
2205
|
+
try {
|
|
2206
|
+
const url = new URL(value);
|
|
2207
|
+
return url.protocol === "https:" && url.origin === value;
|
|
2208
|
+
} catch {
|
|
2209
|
+
return false;
|
|
2210
|
+
}
|
|
2211
|
+
}, "\uC774\uBBF8\uC9C0 \uD5C8\uC6A9 \uBAA9\uB85D\uC5D0\uB294 \uACBD\uB85C \uC5C6\uB294 HTTPS origin\uB9CC \uC120\uC5B8\uD569\uB2C8\uB2E4");
|
|
2212
|
+
var MetadataDefaultRulesSchema = import_zod4.z.object({
|
|
2213
|
+
title: MetadataTextRuleSchema.optional(),
|
|
2214
|
+
titleTemplateMaxLength: import_zod4.z.number().int().min(1).max(1e3).optional(),
|
|
2215
|
+
description: MetadataTextRuleSchema.optional(),
|
|
2216
|
+
/** Code-owned defaults precede site defaults but always follow manual overrides. */
|
|
2217
|
+
defaults: MetadataValuesSchema.optional(),
|
|
2218
|
+
robots: MetadataRobotsPolicySchema.optional(),
|
|
2219
|
+
useFeaturedImage: import_zod4.z.boolean().optional(),
|
|
2220
|
+
useFirstBodyImage: import_zod4.z.boolean().optional(),
|
|
2221
|
+
useTermImage: import_zod4.z.boolean().optional(),
|
|
2222
|
+
imageAltSource: import_zod4.z.enum(["content", "title"]).optional(),
|
|
2223
|
+
legacyImageSource: import_zod4.z.literal("legacy-thumbnail").optional(),
|
|
2224
|
+
imageAllowedOrigins: import_zod4.z.array(imageOrigin).min(1).max(32).optional(),
|
|
2225
|
+
legacyImageAllowedOrigins: import_zod4.z.array(imageOrigin).min(1).max(32).optional(),
|
|
2226
|
+
normalizeCloudflareImages: import_zod4.z.boolean().optional(),
|
|
2227
|
+
fallback: MetadataValuesSchema.optional()
|
|
2228
|
+
}).strict().refine((rule) => !rule.legacyImageSource || Boolean(rule.legacyImageAllowedOrigins?.length), "\uC774\uC804 \uC774\uBBF8\uC9C0 \uC6D0\uBCF8\uC5D0\uB294 \uD5C8\uC6A9 origin \uC120\uC5B8\uC774 \uD544\uC694\uD569\uB2C8\uB2E4");
|
|
2163
2229
|
function metadataContentImageUrl(value, normalizeCloudflareImages = false) {
|
|
2164
2230
|
if (!normalizeCloudflareImages) return value;
|
|
2165
2231
|
try {
|
|
@@ -2199,15 +2265,19 @@ function contentCandidateAllowed(value, input, rule) {
|
|
|
2199
2265
|
function formatMetadataDefaults(input, declaration) {
|
|
2200
2266
|
const dynamic = declaration[input.kind];
|
|
2201
2267
|
const rule = dynamic?.defaultRules?.[input.modelKey];
|
|
2202
|
-
const name =
|
|
2203
|
-
const title =
|
|
2204
|
-
const
|
|
2205
|
-
const
|
|
2268
|
+
const name = cleanMetadataText(input.title, rule?.title);
|
|
2269
|
+
const title = formatMetadataText(input.title, rule?.title, input, name);
|
|
2270
|
+
const template2 = dynamic?.titleTemplates?.[input.modelKey];
|
|
2271
|
+
const templatedTitle = title && template2 ? template2.replace("%s", () => title) : title;
|
|
2272
|
+
const finalTitle = templatedTitle && rule?.titleTemplateMaxLength && templatedTitle.length > rule.titleTemplateMaxLength ? title : templatedTitle;
|
|
2273
|
+
const description = formatMetadataText(input.description, rule?.description, input, name);
|
|
2206
2274
|
const bodyImage = input.firstBodyImage && (input.firstBodyImage.startsWith("https://") || /^\/(?!\/)/u.test(input.firstBodyImage)) ? input.firstBodyImage : void 0;
|
|
2207
|
-
const
|
|
2275
|
+
const featuredImage = (input.kind === "terms" ? rule?.useTermImage : rule?.useFeaturedImage) === false ? void 0 : input.featuredImage;
|
|
2276
|
+
const candidateImage = featuredImage || (rule?.useFirstBodyImage ? bodyImage : void 0) || legacyImageUrl(input.legacyImage, rule);
|
|
2208
2277
|
const ogImage = candidateImage && contentCandidateAllowed(candidateImage, input, rule) ? readMetadataValues({ ogImage: metadataContentImageUrl(candidateImage, rule?.normalizeCloudflareImages) }).ogImage : void 0;
|
|
2209
2278
|
return {
|
|
2210
|
-
...
|
|
2279
|
+
...readMetadataValues(rule?.defaults),
|
|
2280
|
+
...finalTitle ? { title: finalTitle } : {},
|
|
2211
2281
|
...description ? { description } : {},
|
|
2212
2282
|
...ogImage ? { ogImage } : {}
|
|
2213
2283
|
};
|
|
@@ -2343,6 +2413,7 @@ function normalizeRedirectTarget(raw) {
|
|
|
2343
2413
|
isValidSitePatternKey,
|
|
2344
2414
|
issuePreviewToken,
|
|
2345
2415
|
mergeMetadataPatch,
|
|
2416
|
+
metadataBodyParagraphs,
|
|
2346
2417
|
metadataContentImageUrl,
|
|
2347
2418
|
normalizeEmbed,
|
|
2348
2419
|
normalizeHexColor,
|