@roottale/cms-core 0.69.0 → 0.70.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 +217 -143
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8744 -2815
- package/dist/index.d.ts +8744 -2815
- package/dist/index.js +215 -143
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1984,7 +1984,7 @@ function mergeMetadataPatch(current, input) {
|
|
|
1984
1984
|
}
|
|
1985
1985
|
|
|
1986
1986
|
// src/metadata-defaults.ts
|
|
1987
|
-
import { z as
|
|
1987
|
+
import { z as z6 } from "zod";
|
|
1988
1988
|
|
|
1989
1989
|
// src/metadata-text.ts
|
|
1990
1990
|
import { z as z3 } from "zod";
|
|
@@ -2081,113 +2081,30 @@ function formatMetadataText(value, rule, input, name) {
|
|
|
2081
2081
|
return text3;
|
|
2082
2082
|
}
|
|
2083
2083
|
|
|
2084
|
-
// src/metadata-defaults.ts
|
|
2085
|
-
var imageOrigin = z4.string().max(2048).refine((value) => {
|
|
2086
|
-
try {
|
|
2087
|
-
const url = new URL(value);
|
|
2088
|
-
return url.protocol === "https:" && url.origin === value;
|
|
2089
|
-
} catch {
|
|
2090
|
-
return false;
|
|
2091
|
-
}
|
|
2092
|
-
}, "\uC774\uBBF8\uC9C0 \uD5C8\uC6A9 \uBAA9\uB85D\uC5D0\uB294 \uACBD\uB85C \uC5C6\uB294 HTTPS origin\uB9CC \uC120\uC5B8\uD569\uB2C8\uB2E4");
|
|
2093
|
-
var MetadataDefaultRulesSchema = z4.object({
|
|
2094
|
-
title: MetadataTextRuleSchema.optional(),
|
|
2095
|
-
titleTemplateMaxLength: z4.number().int().min(1).max(1e3).optional(),
|
|
2096
|
-
description: MetadataTextRuleSchema.optional(),
|
|
2097
|
-
/** Code-owned defaults precede site defaults but always follow manual overrides. */
|
|
2098
|
-
defaults: MetadataValuesSchema.optional(),
|
|
2099
|
-
robots: MetadataRobotsPolicySchema.optional(),
|
|
2100
|
-
useFeaturedImage: z4.boolean().optional(),
|
|
2101
|
-
useFirstBodyImage: z4.boolean().optional(),
|
|
2102
|
-
useTermImage: z4.boolean().optional(),
|
|
2103
|
-
imageAltSource: z4.enum(["content", "title"]).optional(),
|
|
2104
|
-
legacyImageSource: z4.literal("legacy-thumbnail").optional(),
|
|
2105
|
-
imageAllowedOrigins: z4.array(imageOrigin).min(1).max(32).optional(),
|
|
2106
|
-
legacyImageAllowedOrigins: z4.array(imageOrigin).min(1).max(32).optional(),
|
|
2107
|
-
normalizeCloudflareImages: z4.boolean().optional(),
|
|
2108
|
-
fallback: MetadataValuesSchema.optional()
|
|
2109
|
-
}).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");
|
|
2110
|
-
function metadataContentImageUrl(value, normalizeCloudflareImages = false) {
|
|
2111
|
-
if (!normalizeCloudflareImages) return value;
|
|
2112
|
-
try {
|
|
2113
|
-
const url = new URL(value);
|
|
2114
|
-
const [account, id, variant] = url.pathname.split("/").filter(Boolean);
|
|
2115
|
-
if (url.protocol === "https:" && url.hostname === "imagedelivery.net" && account && id && variant) {
|
|
2116
|
-
url.pathname = `/${account}/${id}/lg`;
|
|
2117
|
-
return url.href;
|
|
2118
|
-
}
|
|
2119
|
-
} catch {
|
|
2120
|
-
}
|
|
2121
|
-
return value;
|
|
2122
|
-
}
|
|
2123
|
-
function legacyImageUrl(value, rule) {
|
|
2124
|
-
if (!value || rule?.legacyImageSource !== "legacy-thumbnail") return void 0;
|
|
2125
|
-
try {
|
|
2126
|
-
const url = new URL(value);
|
|
2127
|
-
if (!rule.legacyImageAllowedOrigins?.includes(url.origin)) return void 0;
|
|
2128
|
-
return readMetadataValues({ ogImage: url.href }).ogImage;
|
|
2129
|
-
} catch {
|
|
2130
|
-
return void 0;
|
|
2131
|
-
}
|
|
2132
|
-
}
|
|
2133
|
-
function contentCandidateAllowed(value, input, rule) {
|
|
2134
|
-
if (!rule?.imageAllowedOrigins) return true;
|
|
2135
|
-
if (value.startsWith("/") && !value.startsWith("//")) return true;
|
|
2136
|
-
try {
|
|
2137
|
-
const url = new URL(value);
|
|
2138
|
-
if (url.protocol !== "https:") return false;
|
|
2139
|
-
const origins = [...rule.imageAllowedOrigins];
|
|
2140
|
-
if (input.imageBaseUrl) origins.push(new URL(input.imageBaseUrl).origin);
|
|
2141
|
-
return origins.includes(url.origin);
|
|
2142
|
-
} catch {
|
|
2143
|
-
return false;
|
|
2144
|
-
}
|
|
2145
|
-
}
|
|
2146
|
-
function formatMetadataDefaults(input, declaration) {
|
|
2147
|
-
const dynamic = declaration[input.kind];
|
|
2148
|
-
const rule = dynamic?.defaultRules?.[input.modelKey];
|
|
2149
|
-
const name = cleanMetadataText(input.title, rule?.title);
|
|
2150
|
-
const title = formatMetadataText(input.title, rule?.title, input, name);
|
|
2151
|
-
const template2 = dynamic?.titleTemplates?.[input.modelKey];
|
|
2152
|
-
const templatedTitle = title && template2 ? template2.replace("%s", () => title) : title;
|
|
2153
|
-
const finalTitle = templatedTitle && rule?.titleTemplateMaxLength && templatedTitle.length > rule.titleTemplateMaxLength ? title : templatedTitle;
|
|
2154
|
-
const description = formatMetadataText(input.description, rule?.description, input, name);
|
|
2155
|
-
const bodyImage = input.firstBodyImage && (input.firstBodyImage.startsWith("https://") || /^\/(?!\/)/u.test(input.firstBodyImage)) ? input.firstBodyImage : void 0;
|
|
2156
|
-
const featuredImage = (input.kind === "terms" ? rule?.useTermImage : rule?.useFeaturedImage) === false ? void 0 : input.featuredImage;
|
|
2157
|
-
const candidateImage = featuredImage || (rule?.useFirstBodyImage ? bodyImage : void 0) || legacyImageUrl(input.legacyImage, rule);
|
|
2158
|
-
const ogImage = candidateImage && contentCandidateAllowed(candidateImage, input, rule) ? readMetadataValues({ ogImage: metadataContentImageUrl(candidateImage, rule?.normalizeCloudflareImages) }).ogImage : void 0;
|
|
2159
|
-
return {
|
|
2160
|
-
...readMetadataValues(rule?.defaults),
|
|
2161
|
-
...finalTitle ? { title: finalTitle } : {},
|
|
2162
|
-
...description ? { description } : {},
|
|
2163
|
-
...ogImage ? { ogImage } : {}
|
|
2164
|
-
};
|
|
2165
|
-
}
|
|
2166
|
-
|
|
2167
|
-
// src/post-body-source.ts
|
|
2168
|
-
function resolvePostBodySource(bodyHtml) {
|
|
2169
|
-
if (typeof bodyHtml !== "string") return { kind: "json" };
|
|
2170
|
-
const trimmed = bodyHtml.trim();
|
|
2171
|
-
if (trimmed.length === 0) return { kind: "json" };
|
|
2172
|
-
const sanitized = sanitizeHtml(trimmed);
|
|
2173
|
-
const visible = /<(?:img|hr|br|iframe|video|audio)\b/i.test(sanitized) || sanitized.replace(/<[^>]*>/g, "").replace(/ /gi, " ").trim().length > 0;
|
|
2174
|
-
return visible ? { kind: "html", html: sanitized } : { kind: "json" };
|
|
2175
|
-
}
|
|
2176
|
-
|
|
2177
2084
|
// src/content-policy-schema.ts
|
|
2178
|
-
import { z as
|
|
2179
|
-
var ContentPolicyFieldNameSchema =
|
|
2180
|
-
var ContentPolicyScalarSchema =
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2085
|
+
import { z as z4 } from "zod";
|
|
2086
|
+
var ContentPolicyFieldNameSchema = z4.string().min(1).max(64).regex(/^[A-Za-z_][A-Za-z0-9_]*$/).refine((name) => name !== "prototype" && !Object.hasOwn(Object.prototype, name));
|
|
2087
|
+
var ContentPolicyScalarSchema = z4.union([
|
|
2088
|
+
z4.string().max(2048),
|
|
2089
|
+
z4.number().finite(),
|
|
2090
|
+
z4.boolean(),
|
|
2091
|
+
z4.null()
|
|
2185
2092
|
]);
|
|
2186
|
-
var
|
|
2093
|
+
var contentPolicyEquality = z4.object({
|
|
2187
2094
|
field: ContentPolicyFieldNameSchema,
|
|
2188
2095
|
equals: ContentPolicyScalarSchema
|
|
2189
2096
|
}).strict();
|
|
2190
|
-
var
|
|
2097
|
+
var ContentPolicyWhenSchema = z4.union([
|
|
2098
|
+
contentPolicyEquality,
|
|
2099
|
+
z4.object({
|
|
2100
|
+
all: z4.array(contentPolicyEquality).min(1).max(4).optional(),
|
|
2101
|
+
unlessAll: z4.array(contentPolicyEquality).min(1).max(4).optional()
|
|
2102
|
+
}).strict().refine(
|
|
2103
|
+
(when2) => when2.all !== void 0 || when2.unlessAll !== void 0,
|
|
2104
|
+
"\uCF58\uD150\uCE20 \uC870\uAC74\uC5D0\uB294 all \uB610\uB294 unlessAll \uADF8\uB8F9\uC774 \uD544\uC694\uD569\uB2C8\uB2E4"
|
|
2105
|
+
)
|
|
2106
|
+
]);
|
|
2107
|
+
var ContentPolicyInternalPathSchema = z4.string().min(1).max(2048).refine((path) => {
|
|
2191
2108
|
if (!path.startsWith("/") || path.startsWith("//") || /[\\?#\u0000-\u0020\u007f]/u.test(path)) return false;
|
|
2192
2109
|
try {
|
|
2193
2110
|
return path.split("/").every((part, index) => {
|
|
@@ -2201,17 +2118,17 @@ var ContentPolicyInternalPathSchema = z5.string().min(1).max(2048).refine((path)
|
|
|
2201
2118
|
}, "\uCF58\uD150\uCE20 \uACBD\uB85C\uB294 \uC548\uC804\uD55C \uC0AC\uC774\uD2B8 \uB0B4\uBD80 \uACBD\uB85C\uC5EC\uC57C \uD569\uB2C8\uB2E4");
|
|
2202
2119
|
var field = { field: ContentPolicyFieldNameSchema };
|
|
2203
2120
|
var scalarChecks = [
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2121
|
+
z4.object({ ...field, op: z4.literal("equals"), value: ContentPolicyScalarSchema }).strict(),
|
|
2122
|
+
z4.object({ ...field, op: z4.literal("notEquals"), value: ContentPolicyScalarSchema }).strict(),
|
|
2123
|
+
z4.object({ ...field, op: z4.literal("oneOf"), values: z4.array(ContentPolicyScalarSchema).min(1).max(16), allowEmpty: z4.boolean().optional() }).strict(),
|
|
2124
|
+
z4.object({ ...field, op: z4.literal("text") }).strict(),
|
|
2125
|
+
z4.object({ ...field, op: z4.literal("date") }).strict(),
|
|
2126
|
+
z4.object({ ...field, op: z4.literal("safeHttps") }).strict(),
|
|
2127
|
+
z4.object({ ...field, op: z4.literal("allowedPath"), paths: z4.array(ContentPolicyInternalPathSchema).min(1).max(512) }).strict()
|
|
2211
2128
|
];
|
|
2212
|
-
var ContentPolicyScalarCheckSchema =
|
|
2129
|
+
var ContentPolicyScalarCheckSchema = z4.discriminatedUnion("op", scalarChecks);
|
|
2213
2130
|
var when = { when: ContentPolicyWhenSchema.optional() };
|
|
2214
|
-
var ContentPolicyCheckSchema =
|
|
2131
|
+
var ContentPolicyCheckSchema = z4.discriminatedUnion("op", [
|
|
2215
2132
|
scalarChecks[0].extend(when),
|
|
2216
2133
|
scalarChecks[1].extend(when),
|
|
2217
2134
|
scalarChecks[2].extend(when),
|
|
@@ -2219,26 +2136,26 @@ var ContentPolicyCheckSchema = z5.discriminatedUnion("op", [
|
|
|
2219
2136
|
scalarChecks[4].extend(when),
|
|
2220
2137
|
scalarChecks[5].extend(when),
|
|
2221
2138
|
scalarChecks[6].extend(when),
|
|
2222
|
-
|
|
2223
|
-
|
|
2139
|
+
z4.object({ ...field, ...when, op: z4.literal("some"), checks: z4.array(ContentPolicyScalarCheckSchema).min(1).max(8) }).strict(),
|
|
2140
|
+
z4.object({
|
|
2224
2141
|
...when,
|
|
2225
|
-
op:
|
|
2226
|
-
allowExcerpt:
|
|
2227
|
-
allowFeaturedImage:
|
|
2228
|
-
additionalUrl:
|
|
2142
|
+
op: z4.literal("bodyContent"),
|
|
2143
|
+
allowExcerpt: z4.boolean().optional(),
|
|
2144
|
+
allowFeaturedImage: z4.boolean().optional(),
|
|
2145
|
+
additionalUrl: z4.object({ ...field, ...when }).strict().optional()
|
|
2229
2146
|
}).strict(),
|
|
2230
|
-
|
|
2147
|
+
z4.object({ ...when, op: z4.literal("bodyText") }).strict()
|
|
2231
2148
|
]);
|
|
2232
|
-
var contentPolicy =
|
|
2233
|
-
visibility:
|
|
2234
|
-
indexing:
|
|
2235
|
-
pathSegmentFormat:
|
|
2149
|
+
var contentPolicy = z4.object({
|
|
2150
|
+
visibility: z4.array(ContentPolicyCheckSchema).max(24),
|
|
2151
|
+
indexing: z4.array(ContentPolicyCheckSchema).max(24),
|
|
2152
|
+
pathSegmentFormat: z4.literal("lower-kebab").optional()
|
|
2236
2153
|
}).strict();
|
|
2237
2154
|
var ContentPolicySchema = contentPolicy.superRefine((policy, ctx) => {
|
|
2238
2155
|
const refs = collectContentPolicyFieldRefs(policy);
|
|
2239
|
-
if (refs.length > 64) ctx.addIssue({ code:
|
|
2156
|
+
if (refs.length > 64) ctx.addIssue({ code: z4.ZodIssueCode.custom, message: "\uCF58\uD150\uCE20 \uC815\uCC45\uC740 \uACF5\uAC1C \uD544\uB4DC 64\uAC1C\uAE4C\uC9C0 \uCC38\uC870\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4" });
|
|
2240
2157
|
for (const ref of refs) {
|
|
2241
|
-
if ((ref.children?.length ?? 0) > 8) ctx.addIssue({ code:
|
|
2158
|
+
if ((ref.children?.length ?? 0) > 8) ctx.addIssue({ code: z4.ZodIssueCode.custom, message: `\uBC18\uBCF5 \uD544\uB4DC ${ref.name}\uC758 \uC790\uC2DD \uCC38\uC870\uB294 8\uAC1C\uAE4C\uC9C0 \uD5C8\uC6A9\uD569\uB2C8\uB2E4` });
|
|
2242
2159
|
}
|
|
2243
2160
|
});
|
|
2244
2161
|
function collectContentPolicyFieldRefs(policy) {
|
|
@@ -2248,17 +2165,43 @@ function collectContentPolicyFieldRefs(policy) {
|
|
|
2248
2165
|
for (const child of children) names.add(child);
|
|
2249
2166
|
refs.set(name, names);
|
|
2250
2167
|
};
|
|
2168
|
+
const addWhen = (when2) => {
|
|
2169
|
+
if (!when2) return;
|
|
2170
|
+
if ("field" in when2) add(when2.field);
|
|
2171
|
+
else for (const condition of [...when2.all ?? [], ...when2.unlessAll ?? []]) add(condition.field);
|
|
2172
|
+
};
|
|
2251
2173
|
for (const check of [...policy.visibility, ...policy.indexing]) {
|
|
2252
|
-
|
|
2174
|
+
addWhen(check.when);
|
|
2253
2175
|
if ("field" in check) add(check.field, check.op === "some" ? check.checks.map((child) => child.field) : []);
|
|
2254
2176
|
if (check.op === "bodyContent" && check.additionalUrl) {
|
|
2255
2177
|
add(check.additionalUrl.field);
|
|
2256
|
-
|
|
2178
|
+
addWhen(check.additionalUrl.when);
|
|
2257
2179
|
}
|
|
2258
2180
|
}
|
|
2259
2181
|
return [...refs].map(([name, children]) => ({ name, ...children.size ? { children: [...children] } : {} }));
|
|
2260
2182
|
}
|
|
2261
2183
|
|
|
2184
|
+
// src/content-policy-condition.ts
|
|
2185
|
+
function matchesContentPolicyWhen(when2, fields) {
|
|
2186
|
+
if (!when2) return true;
|
|
2187
|
+
const matches = ({ field: field3, equals }) => {
|
|
2188
|
+
const value = Object.hasOwn(fields, field3) ? fields[field3] : void 0;
|
|
2189
|
+
return value === equals;
|
|
2190
|
+
};
|
|
2191
|
+
if ("field" in when2) return matches(when2);
|
|
2192
|
+
return (when2.all?.every(matches) ?? true) && !(when2.unlessAll?.every(matches) ?? false);
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2195
|
+
// src/post-body-source.ts
|
|
2196
|
+
function resolvePostBodySource(bodyHtml) {
|
|
2197
|
+
if (typeof bodyHtml !== "string") return { kind: "json" };
|
|
2198
|
+
const trimmed = bodyHtml.trim();
|
|
2199
|
+
if (trimmed.length === 0) return { kind: "json" };
|
|
2200
|
+
const sanitized = sanitizeHtml(trimmed);
|
|
2201
|
+
const visible = /<(?:img|hr|br|iframe|video|audio)\b/i.test(sanitized) || sanitized.replace(/<[^>]*>/g, "").replace(/ /gi, " ").trim().length > 0;
|
|
2202
|
+
return visible ? { kind: "html", html: sanitized } : { kind: "json" };
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2262
2205
|
// src/content-policy.ts
|
|
2263
2206
|
var text2 = (value) => typeof value === "string" ? value.trim() : "";
|
|
2264
2207
|
var record = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
@@ -2321,9 +2264,6 @@ function matchesContentScalarCheck(check, fields) {
|
|
|
2321
2264
|
return check.paths.includes(text2(value));
|
|
2322
2265
|
}
|
|
2323
2266
|
}
|
|
2324
|
-
function matchesWhen(when2, fields) {
|
|
2325
|
-
return !when2 || fieldValue(fields, when2.field) === when2.equals;
|
|
2326
|
-
}
|
|
2327
2267
|
function bodyFacts(post) {
|
|
2328
2268
|
const jsonText = contentPolicyBodyText(post.bodyJson);
|
|
2329
2269
|
const source = resolvePostBodySource(post.bodyHtml);
|
|
@@ -2331,7 +2271,7 @@ function bodyFacts(post) {
|
|
|
2331
2271
|
return { jsonText, html, text: source.kind === "html" ? html.replace(/<[^>]*>/g, "").replace(/ /gi, " ").trim() : jsonText };
|
|
2332
2272
|
}
|
|
2333
2273
|
function matchesCheck(check, post, fields, getBody) {
|
|
2334
|
-
if (!
|
|
2274
|
+
if (!matchesContentPolicyWhen(check.when, fields)) return true;
|
|
2335
2275
|
if (check.op === "some") {
|
|
2336
2276
|
const value = fieldValue(fields, check.field);
|
|
2337
2277
|
return Array.isArray(value) && value.some((item) => {
|
|
@@ -2342,7 +2282,7 @@ function matchesCheck(check, post, fields, getBody) {
|
|
|
2342
2282
|
if (check.op === "bodyText") return Boolean(getBody().text);
|
|
2343
2283
|
if (check.op === "bodyContent") {
|
|
2344
2284
|
const body = getBody();
|
|
2345
|
-
return Boolean(body.jsonText || body.html || check.allowExcerpt && text2(post.excerpt) || check.allowFeaturedImage && contentPolicySafeHttpsUrl(post.featuredImageUrl) || check.additionalUrl &&
|
|
2285
|
+
return Boolean(body.jsonText || body.html || check.allowExcerpt && text2(post.excerpt) || check.allowFeaturedImage && contentPolicySafeHttpsUrl(post.featuredImageUrl) || check.additionalUrl && matchesContentPolicyWhen(check.additionalUrl.when, fields) && contentPolicySafeHttpsUrl(fieldValue(fields, check.additionalUrl.field)));
|
|
2346
2286
|
}
|
|
2347
2287
|
return matchesContentScalarCheck(check, fields);
|
|
2348
2288
|
}
|
|
@@ -2389,21 +2329,151 @@ function evaluateContentPolicy(policy, post, options = {}) {
|
|
|
2389
2329
|
};
|
|
2390
2330
|
}
|
|
2391
2331
|
|
|
2332
|
+
// src/metadata-video-thumbnail.ts
|
|
2333
|
+
import { z as z5 } from "zod";
|
|
2334
|
+
var MetadataVideoThumbnailRuleSchema = z5.object({
|
|
2335
|
+
source: z5.literal("youtube"),
|
|
2336
|
+
field: ContentPolicyFieldNameSchema,
|
|
2337
|
+
when: ContentPolicyWhenSchema.optional()
|
|
2338
|
+
}).strict();
|
|
2339
|
+
function metadataVideoThumbnail(rule, fields) {
|
|
2340
|
+
if (!rule || !matchesContentPolicyWhen(rule.when, fields ?? {})) return void 0;
|
|
2341
|
+
const value = fields && Object.hasOwn(fields, rule.field) ? fields[rule.field] : void 0;
|
|
2342
|
+
const safe = contentPolicySafeHttpsUrl(value);
|
|
2343
|
+
if (!safe) return void 0;
|
|
2344
|
+
const url = new URL(safe);
|
|
2345
|
+
const id = url.hostname === "youtu.be" ? url.pathname.slice(1) : ["www.youtube.com", "youtube.com", "m.youtube.com"].includes(url.hostname) ? url.pathname === "/watch" ? url.searchParams.get("v") : url.pathname.match(/^\/(?:shorts|embed)\/([^/]+)$/)?.[1] : null;
|
|
2346
|
+
return /^[a-zA-Z0-9_-]{11}$/.test(id ?? "") ? `https://i.ytimg.com/vi/${id}/hqdefault.jpg` : void 0;
|
|
2347
|
+
}
|
|
2348
|
+
|
|
2349
|
+
// src/metadata-default-fields.ts
|
|
2350
|
+
function collectMetadataDefaultFieldRefs(rule) {
|
|
2351
|
+
const fields = /* @__PURE__ */ new Set();
|
|
2352
|
+
const video = rule?.videoThumbnail;
|
|
2353
|
+
if (video) {
|
|
2354
|
+
fields.add(video.field);
|
|
2355
|
+
if (video.when) {
|
|
2356
|
+
if ("field" in video.when) fields.add(video.when.field);
|
|
2357
|
+
else for (const condition of [...video.when.all ?? [], ...video.when.unlessAll ?? []]) fields.add(condition.field);
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
for (const text3 of [rule?.title, rule?.description]) {
|
|
2361
|
+
if (text3?.source !== "template") continue;
|
|
2362
|
+
for (const match of (text3.template ?? "").matchAll(/\{\{field:([a-zA-Z][a-zA-Z0-9_-]{0,159})\}\}/gu)) fields.add(match[1]);
|
|
2363
|
+
}
|
|
2364
|
+
return [...fields].map((name) => ({ name }));
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
// src/metadata-defaults.ts
|
|
2368
|
+
var imageOrigin = z6.string().max(2048).refine((value) => {
|
|
2369
|
+
try {
|
|
2370
|
+
const url = new URL(value);
|
|
2371
|
+
return url.protocol === "https:" && url.origin === value;
|
|
2372
|
+
} catch {
|
|
2373
|
+
return false;
|
|
2374
|
+
}
|
|
2375
|
+
}, "\uC774\uBBF8\uC9C0 \uD5C8\uC6A9 \uBAA9\uB85D\uC5D0\uB294 \uACBD\uB85C \uC5C6\uB294 HTTPS origin\uB9CC \uC120\uC5B8\uD569\uB2C8\uB2E4");
|
|
2376
|
+
var MetadataDefaultRulesSchema = z6.object({
|
|
2377
|
+
title: MetadataTextRuleSchema.optional(),
|
|
2378
|
+
titleTemplateMaxLength: z6.number().int().min(1).max(1e3).optional(),
|
|
2379
|
+
description: MetadataTextRuleSchema.optional(),
|
|
2380
|
+
/** Code-owned defaults precede site defaults but always follow manual overrides. */
|
|
2381
|
+
defaults: MetadataValuesSchema.optional(),
|
|
2382
|
+
robots: MetadataRobotsPolicySchema.optional(),
|
|
2383
|
+
useFeaturedImage: z6.boolean().optional(),
|
|
2384
|
+
videoThumbnail: MetadataVideoThumbnailRuleSchema.optional(),
|
|
2385
|
+
useFirstBodyImage: z6.boolean().optional(),
|
|
2386
|
+
useTermImage: z6.boolean().optional(),
|
|
2387
|
+
imageAltSource: z6.enum(["content", "title"]).optional(),
|
|
2388
|
+
legacyImageSource: z6.literal("legacy-thumbnail").optional(),
|
|
2389
|
+
imageAllowedOrigins: z6.array(imageOrigin).min(1).max(32).optional(),
|
|
2390
|
+
legacyImageAllowedOrigins: z6.array(imageOrigin).min(1).max(32).optional(),
|
|
2391
|
+
normalizeCloudflareImages: z6.boolean().optional(),
|
|
2392
|
+
fallback: MetadataValuesSchema.optional()
|
|
2393
|
+
}).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").superRefine((rule, context) => {
|
|
2394
|
+
if (!rule.videoThumbnail) return;
|
|
2395
|
+
const refs = collectMetadataDefaultFieldRefs(rule);
|
|
2396
|
+
if (refs.length > 64) context.addIssue({ code: z6.ZodIssueCode.custom, message: "\uC601\uC0C1 \uC790\uB3D9\uAC12\uC740 \uACF5\uAC1C \uD544\uB4DC 64\uAC1C\uAE4C\uC9C0 \uCC38\uC870\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4" });
|
|
2397
|
+
for (const { name } of refs) {
|
|
2398
|
+
if (!ContentPolicyFieldNameSchema.safeParse(name).success) context.addIssue({
|
|
2399
|
+
code: z6.ZodIssueCode.custom,
|
|
2400
|
+
message: "\uC601\uC0C1 \uC790\uB3D9\uAC12\uC758 \uD14D\uC2A4\uD2B8\xB7\uC870\uAC74\uC740 \uC548\uC804\uD55C \uACF5\uAC1C \uD544\uB4DC \uC774\uB984\uC744 \uC0AC\uC6A9\uD569\uB2C8\uB2E4"
|
|
2401
|
+
});
|
|
2402
|
+
}
|
|
2403
|
+
});
|
|
2404
|
+
function metadataContentImageUrl(value, normalizeCloudflareImages = false) {
|
|
2405
|
+
if (!normalizeCloudflareImages) return value;
|
|
2406
|
+
try {
|
|
2407
|
+
const url = new URL(value);
|
|
2408
|
+
const [account, id, variant] = url.pathname.split("/").filter(Boolean);
|
|
2409
|
+
if (url.protocol === "https:" && url.hostname === "imagedelivery.net" && account && id && variant) {
|
|
2410
|
+
url.pathname = `/${account}/${id}/lg`;
|
|
2411
|
+
return url.href;
|
|
2412
|
+
}
|
|
2413
|
+
} catch {
|
|
2414
|
+
}
|
|
2415
|
+
return value;
|
|
2416
|
+
}
|
|
2417
|
+
function legacyImageUrl(value, rule) {
|
|
2418
|
+
if (!value || rule?.legacyImageSource !== "legacy-thumbnail") return void 0;
|
|
2419
|
+
try {
|
|
2420
|
+
const url = new URL(value);
|
|
2421
|
+
if (!rule.legacyImageAllowedOrigins?.includes(url.origin)) return void 0;
|
|
2422
|
+
return readMetadataValues({ ogImage: url.href }).ogImage;
|
|
2423
|
+
} catch {
|
|
2424
|
+
return void 0;
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
function contentCandidateAllowed(value, input, rule) {
|
|
2428
|
+
if (!rule?.imageAllowedOrigins) return true;
|
|
2429
|
+
if (value.startsWith("/") && !value.startsWith("//")) return true;
|
|
2430
|
+
try {
|
|
2431
|
+
const url = new URL(value);
|
|
2432
|
+
if (url.protocol !== "https:") return false;
|
|
2433
|
+
const origins = [...rule.imageAllowedOrigins];
|
|
2434
|
+
if (input.imageBaseUrl) origins.push(new URL(input.imageBaseUrl).origin);
|
|
2435
|
+
return origins.includes(url.origin);
|
|
2436
|
+
} catch {
|
|
2437
|
+
return false;
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
function formatMetadataDefaults(input, declaration) {
|
|
2441
|
+
const dynamic = declaration[input.kind];
|
|
2442
|
+
const rule = dynamic?.defaultRules?.[input.modelKey];
|
|
2443
|
+
const name = cleanMetadataText(input.title, rule?.title);
|
|
2444
|
+
const title = formatMetadataText(input.title, rule?.title, input, name);
|
|
2445
|
+
const template2 = dynamic?.titleTemplates?.[input.modelKey];
|
|
2446
|
+
const templatedTitle = title && template2 ? template2.replace("%s", () => title) : title;
|
|
2447
|
+
const finalTitle = templatedTitle && rule?.titleTemplateMaxLength && templatedTitle.length > rule.titleTemplateMaxLength ? title : templatedTitle;
|
|
2448
|
+
const description = formatMetadataText(input.description, rule?.description, input, name);
|
|
2449
|
+
const bodyImage = input.firstBodyImage && (input.firstBodyImage.startsWith("https://") || /^\/(?!\/)/u.test(input.firstBodyImage)) ? input.firstBodyImage : void 0;
|
|
2450
|
+
const rawFeaturedImage = (input.kind === "terms" ? rule?.useTermImage : rule?.useFeaturedImage) === false ? void 0 : input.featuredImage;
|
|
2451
|
+
const featuredImage = rule?.videoThumbnail ? contentPolicySafeHttpsUrl(rawFeaturedImage) : rawFeaturedImage;
|
|
2452
|
+
const candidateImage = featuredImage || (input.kind === "posts" ? metadataVideoThumbnail(rule?.videoThumbnail, input.fields) : void 0) || (rule?.useFirstBodyImage ? bodyImage : void 0) || legacyImageUrl(input.legacyImage, rule);
|
|
2453
|
+
const ogImage = candidateImage && contentCandidateAllowed(candidateImage, input, rule) ? readMetadataValues({ ogImage: metadataContentImageUrl(candidateImage, rule?.normalizeCloudflareImages) }).ogImage : void 0;
|
|
2454
|
+
return {
|
|
2455
|
+
...readMetadataValues(rule?.defaults),
|
|
2456
|
+
...finalTitle ? { title: finalTitle } : {},
|
|
2457
|
+
...description ? { description } : {},
|
|
2458
|
+
...ogImage ? { ogImage } : {}
|
|
2459
|
+
};
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2392
2462
|
// src/content-query.ts
|
|
2393
|
-
import { z as
|
|
2463
|
+
import { z as z7 } from "zod";
|
|
2394
2464
|
var field2 = { field: ContentPolicyFieldNameSchema };
|
|
2395
|
-
var ContentQueryFilterSchema =
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2465
|
+
var ContentQueryFilterSchema = z7.discriminatedUnion("op", [
|
|
2466
|
+
z7.object({ ...field2, op: z7.literal("equals"), value: ContentPolicyScalarSchema }).strict(),
|
|
2467
|
+
z7.object({ ...field2, op: z7.literal("notEquals"), value: ContentPolicyScalarSchema }).strict(),
|
|
2468
|
+
z7.object({ ...field2, op: z7.literal("oneOf"), values: z7.array(ContentPolicyScalarSchema).min(1).max(16), allowEmpty: z7.boolean().optional() }).strict()
|
|
2399
2469
|
]);
|
|
2400
|
-
var ContentQuerySchema =
|
|
2401
|
-
modelKeys:
|
|
2402
|
-
filters:
|
|
2403
|
-
scope:
|
|
2470
|
+
var ContentQuerySchema = z7.object({
|
|
2471
|
+
modelKeys: z7.array(z7.string().min(1).max(100).regex(/^[a-z][a-z0-9_-]*$/)).min(1).max(16),
|
|
2472
|
+
filters: z7.array(ContentQueryFilterSchema).max(8).optional(),
|
|
2473
|
+
scope: z7.enum(["currentTerm", "currentPath"]).optional(),
|
|
2404
2474
|
pinnedField: ContentPolicyFieldNameSchema.optional(),
|
|
2405
|
-
pageSize:
|
|
2406
|
-
requireItems:
|
|
2475
|
+
pageSize: z7.number().int().min(1).max(100),
|
|
2476
|
+
requireItems: z7.boolean().optional()
|
|
2407
2477
|
}).strict();
|
|
2408
2478
|
function collectContentQueryFieldRefs(query) {
|
|
2409
2479
|
return [.../* @__PURE__ */ new Set([...(query.filters ?? []).map((filter) => filter.field), ...query.pinnedField ? [query.pinnedField] : []])].map((name) => ({ name }));
|
|
@@ -2582,6 +2652,7 @@ export {
|
|
|
2582
2652
|
cmsParagraphSpacing,
|
|
2583
2653
|
collectContentPolicyFieldRefs,
|
|
2584
2654
|
collectContentQueryFieldRefs,
|
|
2655
|
+
collectMetadataDefaultFieldRefs,
|
|
2585
2656
|
contentPolicyBodyText,
|
|
2586
2657
|
contentPolicySafeHttpsUrl,
|
|
2587
2658
|
createFieldBindingResolver,
|
|
@@ -2616,6 +2687,7 @@ export {
|
|
|
2616
2687
|
mergeMetadataPatch,
|
|
2617
2688
|
metadataBodyParagraphs,
|
|
2618
2689
|
metadataContentImageUrl,
|
|
2690
|
+
metadataVideoThumbnail,
|
|
2619
2691
|
normalizeEmbed,
|
|
2620
2692
|
normalizeHexColor,
|
|
2621
2693
|
normalizeImageAlt,
|