@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/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 z4 } from "zod";
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(/&nbsp;/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 z5 } from "zod";
2179
- var ContentPolicyFieldNameSchema = z5.string().min(1).max(64).regex(/^[A-Za-z_][A-Za-z0-9_]*$/).refine((name) => name !== "prototype" && !Object.hasOwn(Object.prototype, name));
2180
- var ContentPolicyScalarSchema = z5.union([
2181
- z5.string().max(2048),
2182
- z5.number().finite(),
2183
- z5.boolean(),
2184
- z5.null()
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 ContentPolicyWhenSchema = z5.object({
2093
+ var contentPolicyEquality = z4.object({
2187
2094
  field: ContentPolicyFieldNameSchema,
2188
2095
  equals: ContentPolicyScalarSchema
2189
2096
  }).strict();
2190
- var ContentPolicyInternalPathSchema = z5.string().min(1).max(2048).refine((path) => {
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
- z5.object({ ...field, op: z5.literal("equals"), value: ContentPolicyScalarSchema }).strict(),
2205
- z5.object({ ...field, op: z5.literal("notEquals"), value: ContentPolicyScalarSchema }).strict(),
2206
- z5.object({ ...field, op: z5.literal("oneOf"), values: z5.array(ContentPolicyScalarSchema).min(1).max(16), allowEmpty: z5.boolean().optional() }).strict(),
2207
- z5.object({ ...field, op: z5.literal("text") }).strict(),
2208
- z5.object({ ...field, op: z5.literal("date") }).strict(),
2209
- z5.object({ ...field, op: z5.literal("safeHttps") }).strict(),
2210
- z5.object({ ...field, op: z5.literal("allowedPath"), paths: z5.array(ContentPolicyInternalPathSchema).min(1).max(512) }).strict()
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 = z5.discriminatedUnion("op", scalarChecks);
2129
+ var ContentPolicyScalarCheckSchema = z4.discriminatedUnion("op", scalarChecks);
2213
2130
  var when = { when: ContentPolicyWhenSchema.optional() };
2214
- var ContentPolicyCheckSchema = z5.discriminatedUnion("op", [
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
- z5.object({ ...field, ...when, op: z5.literal("some"), checks: z5.array(ContentPolicyScalarCheckSchema).min(1).max(8) }).strict(),
2223
- z5.object({
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: z5.literal("bodyContent"),
2226
- allowExcerpt: z5.boolean().optional(),
2227
- allowFeaturedImage: z5.boolean().optional(),
2228
- additionalUrl: z5.object({ ...field, ...when }).strict().optional()
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
- z5.object({ ...when, op: z5.literal("bodyText") }).strict()
2147
+ z4.object({ ...when, op: z4.literal("bodyText") }).strict()
2231
2148
  ]);
2232
- var contentPolicy = z5.object({
2233
- visibility: z5.array(ContentPolicyCheckSchema).max(24),
2234
- indexing: z5.array(ContentPolicyCheckSchema).max(24),
2235
- pathSegmentFormat: z5.literal("lower-kebab").optional()
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: z5.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" });
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: z5.ZodIssueCode.custom, message: `\uBC18\uBCF5 \uD544\uB4DC ${ref.name}\uC758 \uC790\uC2DD \uCC38\uC870\uB294 8\uAC1C\uAE4C\uC9C0 \uD5C8\uC6A9\uD569\uB2C8\uB2E4` });
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
- if (check.when) add(check.when.field);
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
- if (check.additionalUrl.when) add(check.additionalUrl.when.field);
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(/&nbsp;/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(/&nbsp;/gi, " ").trim() : jsonText };
2332
2272
  }
2333
2273
  function matchesCheck(check, post, fields, getBody) {
2334
- if (!matchesWhen(check.when, fields)) return true;
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 && matchesWhen(check.additionalUrl.when, fields) && contentPolicySafeHttpsUrl(fieldValue(fields, check.additionalUrl.field)));
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 z6 } from "zod";
2463
+ import { z as z7 } from "zod";
2394
2464
  var field2 = { field: ContentPolicyFieldNameSchema };
2395
- var ContentQueryFilterSchema = z6.discriminatedUnion("op", [
2396
- z6.object({ ...field2, op: z6.literal("equals"), value: ContentPolicyScalarSchema }).strict(),
2397
- z6.object({ ...field2, op: z6.literal("notEquals"), value: ContentPolicyScalarSchema }).strict(),
2398
- z6.object({ ...field2, op: z6.literal("oneOf"), values: z6.array(ContentPolicyScalarSchema).min(1).max(16), allowEmpty: z6.boolean().optional() }).strict()
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 = z6.object({
2401
- modelKeys: z6.array(z6.string().min(1).max(100).regex(/^[a-z][a-z0-9_-]*$/)).min(1).max(16),
2402
- filters: z6.array(ContentQueryFilterSchema).max(8).optional(),
2403
- scope: z6.enum(["currentTerm", "currentPath"]).optional(),
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: z6.number().int().min(1).max(100),
2406
- requireItems: z6.boolean().optional()
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,