@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/dist/index.js
CHANGED
|
@@ -1960,42 +1960,37 @@ function mergeMetadataPatch(current, input) {
|
|
|
1960
1960
|
}
|
|
1961
1961
|
|
|
1962
1962
|
// src/metadata-defaults.ts
|
|
1963
|
+
import { z as z4 } from "zod";
|
|
1964
|
+
|
|
1965
|
+
// src/metadata-text.ts
|
|
1963
1966
|
import { z as z3 } from "zod";
|
|
1964
1967
|
var fallbackText = z3.string().max(2e3);
|
|
1965
|
-
var
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
return false;
|
|
1971
|
-
}
|
|
1972
|
-
}, "\uC774\uBBF8\uC9C0 \uD5C8\uC6A9 \uBAA9\uB85D\uC5D0\uB294 \uACBD\uB85C \uC5C6\uB294 HTTPS origin\uB9CC \uC120\uC5B8\uD569\uB2C8\uB2E4");
|
|
1968
|
+
var templateToken = /\{\{(?:title|field:[a-zA-Z][a-zA-Z0-9_-]{0,159})\}\}/gu;
|
|
1969
|
+
var template = fallbackText.refine(
|
|
1970
|
+
(value) => !/[{}]/u.test(value.replace(templateToken, "")),
|
|
1971
|
+
"\uC790\uB3D9 \uD14D\uC2A4\uD2B8\uC5D0\uB294 {{title}} \uB610\uB294 {{field:\uD544\uB4DC\uC774\uB984}} \uD1A0\uD070\uB9CC \uC0AC\uC6A9\uD569\uB2C8\uB2E4"
|
|
1972
|
+
);
|
|
1973
1973
|
var MetadataTextRuleSchema = z3.object({
|
|
1974
|
-
source: z3.enum(["content", "fallback"]).optional(),
|
|
1974
|
+
source: z3.enum(["content", "fallback", "template"]).optional(),
|
|
1975
|
+
template: template.optional(),
|
|
1975
1976
|
stripLeadingLabel: z3.enum(["question", "answer"]).optional(),
|
|
1976
1977
|
replaceLineBreakTokens: z3.boolean().optional(),
|
|
1977
1978
|
removeEmDashes: z3.boolean().optional(),
|
|
1978
1979
|
normalizeWhitespace: z3.boolean().optional(),
|
|
1979
1980
|
maxLength: z3.number().int().min(1).max(2e3).optional(),
|
|
1981
|
+
lengthUnit: z3.enum(["code-units", "code-points"]).optional(),
|
|
1982
|
+
wordBoundaryMinLength: z3.number().int().min(1).max(2e3).optional(),
|
|
1980
1983
|
ellipsis: z3.string().max(10).optional(),
|
|
1984
|
+
bodyFallback: z3.object({
|
|
1985
|
+
preferContaining: z3.string().trim().min(1).max(100).optional(),
|
|
1986
|
+
stripLeadingLabel: z3.literal("conclusion").optional()
|
|
1987
|
+
}).strict().optional(),
|
|
1981
1988
|
fallback: fallbackText.optional(),
|
|
1982
1989
|
fallbackWithParent: fallbackText.optional(),
|
|
1983
1990
|
fallbackWithoutParent: fallbackText.optional(),
|
|
1984
1991
|
fallbackBySlug: z3.record(z3.string().max(200), fallbackText).optional()
|
|
1985
|
-
}).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");
|
|
1986
|
-
|
|
1987
|
-
title: MetadataTextRuleSchema.optional(),
|
|
1988
|
-
description: MetadataTextRuleSchema.optional(),
|
|
1989
|
-
useFirstBodyImage: z3.boolean().optional(),
|
|
1990
|
-
useTermImage: z3.boolean().optional(),
|
|
1991
|
-
imageAltSource: z3.enum(["content", "title"]).optional(),
|
|
1992
|
-
legacyImageSource: z3.literal("legacy-thumbnail").optional(),
|
|
1993
|
-
imageAllowedOrigins: z3.array(imageOrigin).min(1).max(32).optional(),
|
|
1994
|
-
legacyImageAllowedOrigins: z3.array(imageOrigin).min(1).max(32).optional(),
|
|
1995
|
-
normalizeCloudflareImages: z3.boolean().optional(),
|
|
1996
|
-
fallback: MetadataValuesSchema.optional()
|
|
1997
|
-
}).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");
|
|
1998
|
-
function cleanText(value, rule) {
|
|
1992
|
+
}).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");
|
|
1993
|
+
function cleanMetadataText(value, rule) {
|
|
1999
1994
|
let text2 = value.trim();
|
|
2000
1995
|
if (rule?.stripLeadingLabel === "question") text2 = text2.replace(/^\s*Q[..:]?\s*/iu, "");
|
|
2001
1996
|
if (rule?.stripLeadingLabel === "answer") text2 = text2.replace(/^\s*A[..:]?\s*/iu, "");
|
|
@@ -2004,20 +1999,90 @@ function cleanText(value, rule) {
|
|
|
2004
1999
|
if (rule?.normalizeWhitespace) text2 = text2.replace(/\s+/gu, " ");
|
|
2005
2000
|
return text2.trim();
|
|
2006
2001
|
}
|
|
2007
|
-
function
|
|
2008
|
-
|
|
2002
|
+
function metadataBodyParagraphs(value) {
|
|
2003
|
+
const paragraphs = [];
|
|
2004
|
+
const textParts = (node) => {
|
|
2005
|
+
if (Array.isArray(node)) return node.flatMap(textParts);
|
|
2006
|
+
if (!node || typeof node !== "object") return [];
|
|
2007
|
+
const item = node;
|
|
2008
|
+
return [...typeof item.text === "string" ? [item.text] : [], ...textParts(item.content)];
|
|
2009
|
+
};
|
|
2010
|
+
const walk = (node) => {
|
|
2011
|
+
if (Array.isArray(node)) {
|
|
2012
|
+
node.forEach(walk);
|
|
2013
|
+
return;
|
|
2014
|
+
}
|
|
2015
|
+
if (!node || typeof node !== "object") return;
|
|
2016
|
+
const item = node;
|
|
2017
|
+
if (item.type === "paragraph") {
|
|
2018
|
+
const text2 = textParts(item.content).join(" ").replace(/\s+/gu, " ").trim();
|
|
2019
|
+
if (text2) paragraphs.push(text2);
|
|
2020
|
+
} else walk(item.content);
|
|
2021
|
+
};
|
|
2022
|
+
walk(value);
|
|
2023
|
+
return paragraphs;
|
|
2024
|
+
}
|
|
2025
|
+
function formatMetadataText(value, rule, input, name) {
|
|
2026
|
+
let text2 = rule?.source === "fallback" ? "" : cleanMetadataText(value ?? "", rule);
|
|
2027
|
+
if (rule?.source === "template") {
|
|
2028
|
+
text2 = cleanMetadataText((rule.template ?? "").replace(templateToken, (token) => {
|
|
2029
|
+
if (token === "{{title}}") return name;
|
|
2030
|
+
const field = token.slice(8, -2);
|
|
2031
|
+
const value2 = Object.hasOwn(input.fields ?? {}, field) ? input.fields?.[field] : void 0;
|
|
2032
|
+
return typeof value2 === "string" ? value2.trim() : "";
|
|
2033
|
+
}), rule);
|
|
2034
|
+
}
|
|
2035
|
+
if (!text2 && rule?.bodyFallback) {
|
|
2036
|
+
const paragraphs = input.bodyParagraphs ?? [];
|
|
2037
|
+
const preferred = rule.bodyFallback.preferContaining;
|
|
2038
|
+
text2 = (preferred ? paragraphs.find((paragraph) => paragraph.includes(preferred)) : void 0) ?? paragraphs[0] ?? "";
|
|
2039
|
+
if (rule.bodyFallback.stripLeadingLabel === "conclusion") text2 = text2.replace(/^(먼저\s*)?결론부터\s*(말씀드리면|말씀드릴게요)[.!?]?\s*/u, "");
|
|
2040
|
+
text2 = cleanMetadataText(text2, rule);
|
|
2041
|
+
}
|
|
2009
2042
|
if (!text2) {
|
|
2010
|
-
const
|
|
2011
|
-
|
|
2043
|
+
const slugFallback = input.slug && Object.hasOwn(rule?.fallbackBySlug ?? {}, input.slug) ? rule?.fallbackBySlug?.[input.slug] : void 0;
|
|
2044
|
+
const fallback = input.parentId ? rule?.fallbackWithParent ?? rule?.fallback : slugFallback ?? rule?.fallbackWithoutParent ?? rule?.fallback;
|
|
2045
|
+
text2 = cleanMetadataText((fallback ?? "").replaceAll("%s", () => name), rule);
|
|
2012
2046
|
}
|
|
2013
2047
|
if (!text2) return void 0;
|
|
2014
|
-
|
|
2048
|
+
const units = rule?.lengthUnit === "code-points" ? [...text2] : text2.split("");
|
|
2049
|
+
if (rule?.maxLength && units.length > rule.maxLength) {
|
|
2015
2050
|
const suffix = rule.ellipsis ?? "";
|
|
2016
|
-
const
|
|
2051
|
+
const suffixLength = rule.lengthUnit === "code-points" ? [...suffix].length : suffix.length;
|
|
2052
|
+
let head = units.slice(0, rule.maxLength - suffixLength).join("");
|
|
2053
|
+
const boundary = head.lastIndexOf(" ");
|
|
2054
|
+
if (rule.wordBoundaryMinLength && boundary >= rule.wordBoundaryMinLength) head = head.slice(0, boundary);
|
|
2017
2055
|
return suffix ? head.trimEnd() + suffix : head;
|
|
2018
2056
|
}
|
|
2019
2057
|
return text2;
|
|
2020
2058
|
}
|
|
2059
|
+
|
|
2060
|
+
// src/metadata-defaults.ts
|
|
2061
|
+
var imageOrigin = z4.string().max(2048).refine((value) => {
|
|
2062
|
+
try {
|
|
2063
|
+
const url = new URL(value);
|
|
2064
|
+
return url.protocol === "https:" && url.origin === value;
|
|
2065
|
+
} catch {
|
|
2066
|
+
return false;
|
|
2067
|
+
}
|
|
2068
|
+
}, "\uC774\uBBF8\uC9C0 \uD5C8\uC6A9 \uBAA9\uB85D\uC5D0\uB294 \uACBD\uB85C \uC5C6\uB294 HTTPS origin\uB9CC \uC120\uC5B8\uD569\uB2C8\uB2E4");
|
|
2069
|
+
var MetadataDefaultRulesSchema = z4.object({
|
|
2070
|
+
title: MetadataTextRuleSchema.optional(),
|
|
2071
|
+
titleTemplateMaxLength: z4.number().int().min(1).max(1e3).optional(),
|
|
2072
|
+
description: MetadataTextRuleSchema.optional(),
|
|
2073
|
+
/** Code-owned defaults precede site defaults but always follow manual overrides. */
|
|
2074
|
+
defaults: MetadataValuesSchema.optional(),
|
|
2075
|
+
robots: MetadataRobotsPolicySchema.optional(),
|
|
2076
|
+
useFeaturedImage: z4.boolean().optional(),
|
|
2077
|
+
useFirstBodyImage: z4.boolean().optional(),
|
|
2078
|
+
useTermImage: z4.boolean().optional(),
|
|
2079
|
+
imageAltSource: z4.enum(["content", "title"]).optional(),
|
|
2080
|
+
legacyImageSource: z4.literal("legacy-thumbnail").optional(),
|
|
2081
|
+
imageAllowedOrigins: z4.array(imageOrigin).min(1).max(32).optional(),
|
|
2082
|
+
legacyImageAllowedOrigins: z4.array(imageOrigin).min(1).max(32).optional(),
|
|
2083
|
+
normalizeCloudflareImages: z4.boolean().optional(),
|
|
2084
|
+
fallback: MetadataValuesSchema.optional()
|
|
2085
|
+
}).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");
|
|
2021
2086
|
function metadataContentImageUrl(value, normalizeCloudflareImages = false) {
|
|
2022
2087
|
if (!normalizeCloudflareImages) return value;
|
|
2023
2088
|
try {
|
|
@@ -2057,15 +2122,19 @@ function contentCandidateAllowed(value, input, rule) {
|
|
|
2057
2122
|
function formatMetadataDefaults(input, declaration) {
|
|
2058
2123
|
const dynamic = declaration[input.kind];
|
|
2059
2124
|
const rule = dynamic?.defaultRules?.[input.modelKey];
|
|
2060
|
-
const name =
|
|
2061
|
-
const title =
|
|
2062
|
-
const
|
|
2063
|
-
const
|
|
2125
|
+
const name = cleanMetadataText(input.title, rule?.title);
|
|
2126
|
+
const title = formatMetadataText(input.title, rule?.title, input, name);
|
|
2127
|
+
const template2 = dynamic?.titleTemplates?.[input.modelKey];
|
|
2128
|
+
const templatedTitle = title && template2 ? template2.replace("%s", () => title) : title;
|
|
2129
|
+
const finalTitle = templatedTitle && rule?.titleTemplateMaxLength && templatedTitle.length > rule.titleTemplateMaxLength ? title : templatedTitle;
|
|
2130
|
+
const description = formatMetadataText(input.description, rule?.description, input, name);
|
|
2064
2131
|
const bodyImage = input.firstBodyImage && (input.firstBodyImage.startsWith("https://") || /^\/(?!\/)/u.test(input.firstBodyImage)) ? input.firstBodyImage : void 0;
|
|
2065
|
-
const
|
|
2132
|
+
const featuredImage = (input.kind === "terms" ? rule?.useTermImage : rule?.useFeaturedImage) === false ? void 0 : input.featuredImage;
|
|
2133
|
+
const candidateImage = featuredImage || (rule?.useFirstBodyImage ? bodyImage : void 0) || legacyImageUrl(input.legacyImage, rule);
|
|
2066
2134
|
const ogImage = candidateImage && contentCandidateAllowed(candidateImage, input, rule) ? readMetadataValues({ ogImage: metadataContentImageUrl(candidateImage, rule?.normalizeCloudflareImages) }).ogImage : void 0;
|
|
2067
2135
|
return {
|
|
2068
|
-
...
|
|
2136
|
+
...readMetadataValues(rule?.defaults),
|
|
2137
|
+
...finalTitle ? { title: finalTitle } : {},
|
|
2069
2138
|
...description ? { description } : {},
|
|
2070
2139
|
...ogImage ? { ogImage } : {}
|
|
2071
2140
|
};
|
|
@@ -2200,6 +2269,7 @@ export {
|
|
|
2200
2269
|
isValidSitePatternKey,
|
|
2201
2270
|
issuePreviewToken,
|
|
2202
2271
|
mergeMetadataPatch,
|
|
2272
|
+
metadataBodyParagraphs,
|
|
2203
2273
|
metadataContentImageUrl,
|
|
2204
2274
|
normalizeEmbed,
|
|
2205
2275
|
normalizeHexColor,
|