@cms0/cms0 0.2.7 → 0.2.9
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/README.md +28 -3
- package/dist/cjs/custom-types/registry.cjs +250 -5
- package/dist/cjs/index.cjs +342 -18
- package/dist/cjs/libs/cli/descriptor-builder-alt.cjs +237 -5
- package/dist/cjs/seo.cjs +226 -0
- package/dist/esm/custom-types/registry.js +250 -5
- package/dist/esm/index.js +338 -19
- package/dist/esm/libs/cli/descriptor-builder-alt.js +237 -5
- package/dist/esm/seo.js +220 -0
- package/dist/types/custom-types/index.d.ts +38 -0
- package/dist/types/custom-types/index.d.ts.map +1 -1
- package/dist/types/custom-types/registry.d.ts +19 -1
- package/dist/types/custom-types/registry.d.ts.map +1 -1
- package/dist/types/index.d.ts +39 -13
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/libs/cli/descriptor-builder-alt.d.ts.map +1 -1
- package/dist/types/seo.d.ts +18 -0
- package/dist/types/seo.d.ts.map +1 -0
- package/package.json +2 -2
|
@@ -8,6 +8,7 @@ exports.buildDescriptorAlt = buildDescriptorAlt;
|
|
|
8
8
|
const fs_1 = __importDefault(require("fs"));
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
10
10
|
const ts_morph_1 = require("ts-morph");
|
|
11
|
+
const shared_1 = require("@cms0/shared");
|
|
11
12
|
const config_loader_js_1 = require("./config-loader.cjs");
|
|
12
13
|
const registry_js_1 = require("../../custom-types/registry.cjs");
|
|
13
14
|
function resolveCustomTypeName(type) {
|
|
@@ -127,6 +128,39 @@ function descriptorFromType(type, modelNames, warnings, ctx, opts) {
|
|
|
127
128
|
const { base, optional, nullable } = unwrapOptional(type);
|
|
128
129
|
const isOptional = !!opts?.optional || optional;
|
|
129
130
|
const isNullable = !!opts?.nullable || nullable;
|
|
131
|
+
if (base.isUnion()) {
|
|
132
|
+
const unionDescriptor = descriptorFromUnionType(base, modelNames, warnings, ctx);
|
|
133
|
+
if (unionDescriptor) {
|
|
134
|
+
return applyOptionalityToDescriptor(unionDescriptor, isOptional, isNullable);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (base.isStringLiteral()) {
|
|
138
|
+
return {
|
|
139
|
+
kind: "enum",
|
|
140
|
+
valueType: "string",
|
|
141
|
+
values: [String(base.getLiteralValue())],
|
|
142
|
+
optional: isOptional,
|
|
143
|
+
nullable: isNullable,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
if (base.isNumberLiteral()) {
|
|
147
|
+
return {
|
|
148
|
+
kind: "enum",
|
|
149
|
+
valueType: "number",
|
|
150
|
+
values: [Number(base.getLiteralValue())],
|
|
151
|
+
optional: isOptional,
|
|
152
|
+
nullable: isNullable,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
if (base.isBooleanLiteral()) {
|
|
156
|
+
return {
|
|
157
|
+
kind: "enum",
|
|
158
|
+
valueType: "boolean",
|
|
159
|
+
values: [base.getText() === "true"],
|
|
160
|
+
optional: isOptional,
|
|
161
|
+
nullable: isNullable,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
130
164
|
if (base.isString()) {
|
|
131
165
|
return {
|
|
132
166
|
kind: "primitive",
|
|
@@ -195,6 +229,203 @@ function descriptorFromType(type, modelNames, warnings, ctx, opts) {
|
|
|
195
229
|
nullable: isNullable,
|
|
196
230
|
};
|
|
197
231
|
}
|
|
232
|
+
function descriptorFromUnionType(unionType, modelNames, warnings, ctx) {
|
|
233
|
+
const unionTypes = unionType.getUnionTypes();
|
|
234
|
+
if (!unionTypes.length)
|
|
235
|
+
return undefined;
|
|
236
|
+
const hasBroadString = unionTypes.some((entry) => entry.isString());
|
|
237
|
+
const hasBroadNumber = unionTypes.some((entry) => entry.isNumber());
|
|
238
|
+
const hasBroadBoolean = unionTypes.some((entry) => entry.isBoolean());
|
|
239
|
+
const filtered = unionTypes.filter((entry) => {
|
|
240
|
+
if (entry.isStringLiteral() && hasBroadString)
|
|
241
|
+
return false;
|
|
242
|
+
if (entry.isNumberLiteral() && hasBroadNumber)
|
|
243
|
+
return false;
|
|
244
|
+
if (entry.isBooleanLiteral() && hasBroadBoolean)
|
|
245
|
+
return false;
|
|
246
|
+
return true;
|
|
247
|
+
});
|
|
248
|
+
if (!filtered.length) {
|
|
249
|
+
if (hasBroadString) {
|
|
250
|
+
return { kind: "primitive", type: "string" };
|
|
251
|
+
}
|
|
252
|
+
if (hasBroadNumber) {
|
|
253
|
+
return { kind: "primitive", type: "number" };
|
|
254
|
+
}
|
|
255
|
+
if (hasBroadBoolean) {
|
|
256
|
+
return { kind: "primitive", type: "boolean" };
|
|
257
|
+
}
|
|
258
|
+
return undefined;
|
|
259
|
+
}
|
|
260
|
+
const literalEnum = resolveLiteralUnionAsEnum(filtered);
|
|
261
|
+
if (literalEnum) {
|
|
262
|
+
return literalEnum;
|
|
263
|
+
}
|
|
264
|
+
const branches = filtered
|
|
265
|
+
.map((entry, index) => descriptorFromType(entry, modelNames, warnings, `${ctx}|${index}`, {
|
|
266
|
+
optional: false,
|
|
267
|
+
nullable: false,
|
|
268
|
+
}))
|
|
269
|
+
.map((entry) => stripOptionality(entry));
|
|
270
|
+
const deduped = dedupeDescriptors(branches);
|
|
271
|
+
if (!deduped.length)
|
|
272
|
+
return undefined;
|
|
273
|
+
if (deduped.length === 1)
|
|
274
|
+
return deduped[0];
|
|
275
|
+
const discriminator = inferUnionDiscriminator(deduped);
|
|
276
|
+
const branchKeys = (0, shared_1.computeUnionBranchKeys)(deduped);
|
|
277
|
+
return {
|
|
278
|
+
kind: "union",
|
|
279
|
+
anyOf: deduped,
|
|
280
|
+
branchKeys,
|
|
281
|
+
...(discriminator ? { discriminator: { key: discriminator } } : {}),
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
function stripOptionality(descriptor) {
|
|
285
|
+
const clone = JSON.parse(JSON.stringify(descriptor));
|
|
286
|
+
delete clone.optional;
|
|
287
|
+
delete clone.nullable;
|
|
288
|
+
return clone;
|
|
289
|
+
}
|
|
290
|
+
function dedupeDescriptors(descriptors) {
|
|
291
|
+
const seen = new Set();
|
|
292
|
+
const deduped = [];
|
|
293
|
+
for (const descriptor of descriptors) {
|
|
294
|
+
const key = JSON.stringify(descriptor);
|
|
295
|
+
if (seen.has(key))
|
|
296
|
+
continue;
|
|
297
|
+
seen.add(key);
|
|
298
|
+
deduped.push(descriptor);
|
|
299
|
+
}
|
|
300
|
+
return deduped;
|
|
301
|
+
}
|
|
302
|
+
function resolveLiteralUnionAsEnum(types) {
|
|
303
|
+
if (!types.length)
|
|
304
|
+
return undefined;
|
|
305
|
+
const literals = [];
|
|
306
|
+
let kind;
|
|
307
|
+
for (const entry of types) {
|
|
308
|
+
if (entry.isStringLiteral()) {
|
|
309
|
+
if (kind && kind !== "string")
|
|
310
|
+
return undefined;
|
|
311
|
+
kind = "string";
|
|
312
|
+
literals.push(String(entry.getLiteralValue()));
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
if (entry.isNumberLiteral()) {
|
|
316
|
+
if (kind && kind !== "number")
|
|
317
|
+
return undefined;
|
|
318
|
+
kind = "number";
|
|
319
|
+
literals.push(Number(entry.getLiteralValue()));
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
if (entry.isBooleanLiteral()) {
|
|
323
|
+
if (kind && kind !== "boolean")
|
|
324
|
+
return undefined;
|
|
325
|
+
kind = "boolean";
|
|
326
|
+
literals.push(entry.getText() === "true");
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
return undefined;
|
|
330
|
+
}
|
|
331
|
+
if (!kind)
|
|
332
|
+
return undefined;
|
|
333
|
+
const unique = Array.from(new Set(literals));
|
|
334
|
+
return {
|
|
335
|
+
kind: "enum",
|
|
336
|
+
valueType: kind,
|
|
337
|
+
values: unique,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
function inferUnionDiscriminator(descriptors) {
|
|
341
|
+
if (!descriptors.length ||
|
|
342
|
+
descriptors.some((entry) => entry?.type !== "object")) {
|
|
343
|
+
return undefined;
|
|
344
|
+
}
|
|
345
|
+
const objects = descriptors;
|
|
346
|
+
const sharedKeys = Object.keys(objects[0]?.properties ?? {}).filter((key) => objects.every((obj) => Object.prototype.hasOwnProperty.call(obj.properties ?? {}, key)));
|
|
347
|
+
for (const key of sharedKeys) {
|
|
348
|
+
const enumLikeValues = objects.map((obj) => {
|
|
349
|
+
const prop = (obj.properties ?? {})[key];
|
|
350
|
+
if (!prop)
|
|
351
|
+
return undefined;
|
|
352
|
+
if (prop.kind !== "enum" || !Array.isArray(prop.values) || prop.values.length !== 1) {
|
|
353
|
+
return undefined;
|
|
354
|
+
}
|
|
355
|
+
return prop.values[0];
|
|
356
|
+
});
|
|
357
|
+
if (enumLikeValues.some((value) => value === undefined)) {
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
const unique = new Set(enumLikeValues);
|
|
361
|
+
if (unique.size === enumLikeValues.length) {
|
|
362
|
+
return key;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return undefined;
|
|
366
|
+
}
|
|
367
|
+
function normalizePropertyOrder(keys, explicitOrder) {
|
|
368
|
+
const normalizedExplicit = Array.isArray(explicitOrder)
|
|
369
|
+
? explicitOrder
|
|
370
|
+
.filter((value) => typeof value === "string")
|
|
371
|
+
.map((value) => value.trim())
|
|
372
|
+
.filter(Boolean)
|
|
373
|
+
: [];
|
|
374
|
+
const dedupedExplicit = Array.from(new Set(normalizedExplicit)).filter((key) => keys.includes(key));
|
|
375
|
+
const remaining = keys.filter((key) => !dedupedExplicit.includes(key));
|
|
376
|
+
return [...dedupedExplicit, ...remaining];
|
|
377
|
+
}
|
|
378
|
+
function applyFieldPropertyOrder(descriptor) {
|
|
379
|
+
if (!descriptor || typeof descriptor !== "object")
|
|
380
|
+
return descriptor;
|
|
381
|
+
if (descriptor.type === "object") {
|
|
382
|
+
const properties = (descriptor.properties ?? {});
|
|
383
|
+
const keys = Object.keys(properties);
|
|
384
|
+
for (const key of keys) {
|
|
385
|
+
properties[key] = applyFieldPropertyOrder(properties[key]);
|
|
386
|
+
}
|
|
387
|
+
descriptor.propertyOrder = normalizePropertyOrder(keys, descriptor.propertyOrder);
|
|
388
|
+
return descriptor;
|
|
389
|
+
}
|
|
390
|
+
if (descriptor.type === "array" && descriptor.items) {
|
|
391
|
+
descriptor.items = applyFieldPropertyOrder(descriptor.items);
|
|
392
|
+
return descriptor;
|
|
393
|
+
}
|
|
394
|
+
if (descriptor.kind === "union" && Array.isArray(descriptor.anyOf)) {
|
|
395
|
+
descriptor.anyOf = descriptor.anyOf.map((branch) => applyFieldPropertyOrder(branch));
|
|
396
|
+
return descriptor;
|
|
397
|
+
}
|
|
398
|
+
return descriptor;
|
|
399
|
+
}
|
|
400
|
+
function applyDescriptorOrderingMetadata(descriptor) {
|
|
401
|
+
const rootsOrder = Object.keys(descriptor.roots ?? {});
|
|
402
|
+
const modelsOrder = Object.keys(descriptor.models ?? {});
|
|
403
|
+
for (const modelName of modelsOrder) {
|
|
404
|
+
const model = descriptor.models?.[modelName];
|
|
405
|
+
if (!model || typeof model !== "object")
|
|
406
|
+
continue;
|
|
407
|
+
const keys = Object.keys(model.properties ?? {});
|
|
408
|
+
for (const key of keys) {
|
|
409
|
+
model.properties[key] = applyFieldPropertyOrder(model.properties[key]);
|
|
410
|
+
}
|
|
411
|
+
model.propertyOrder = normalizePropertyOrder(keys, model.propertyOrder);
|
|
412
|
+
}
|
|
413
|
+
for (const rootName of rootsOrder) {
|
|
414
|
+
const rootDescriptor = descriptor.roots?.[rootName];
|
|
415
|
+
if (!rootDescriptor)
|
|
416
|
+
continue;
|
|
417
|
+
descriptor.roots[rootName] = applyFieldPropertyOrder(rootDescriptor);
|
|
418
|
+
}
|
|
419
|
+
descriptor.metadata = {
|
|
420
|
+
...(descriptor.metadata ?? {}),
|
|
421
|
+
ordering: {
|
|
422
|
+
...(descriptor.metadata?.ordering ?? {}),
|
|
423
|
+
roots: rootsOrder,
|
|
424
|
+
models: modelsOrder,
|
|
425
|
+
},
|
|
426
|
+
};
|
|
427
|
+
return descriptor;
|
|
428
|
+
}
|
|
198
429
|
function collectModels(sourceFiles, modelNames, modelMap, warnings) {
|
|
199
430
|
const handleShape = (name, type, ctx) => {
|
|
200
431
|
if (modelMap[name])
|
|
@@ -296,15 +527,15 @@ function buildDescriptorAlt(resolved) {
|
|
|
296
527
|
const referencedModelNames = new Set();
|
|
297
528
|
collectReferencedModelNames(rootType, referencedModelNames, new Set(), "root");
|
|
298
529
|
const referencedCustomTypeNames = new Set(Array.from(referencedModelNames).filter((n) => registry_js_1.customTypeNames.has(n)));
|
|
299
|
-
const
|
|
300
|
-
const
|
|
530
|
+
const resolvedCustomTypeNames = (0, registry_js_1.resolveCustomTypeDependencies)(referencedCustomTypeNames);
|
|
531
|
+
const resolvedCustomModelNames = new Set(Array.from(resolvedCustomTypeNames).filter((n) => registry_js_1.customModelTypeNames.has(n)));
|
|
301
532
|
const modelNames = new Set([
|
|
302
533
|
...Array.from(referencedModelNames).filter((n) => exportedModelNames.has(n)),
|
|
303
|
-
...Array.from(
|
|
534
|
+
...Array.from(resolvedCustomModelNames),
|
|
304
535
|
]);
|
|
305
536
|
const modelMap = {};
|
|
306
537
|
collectModels(sourceFiles, modelNames, modelMap, warnings);
|
|
307
|
-
|
|
538
|
+
resolvedCustomModelNames.forEach((name) => {
|
|
308
539
|
if (modelMap[name]) {
|
|
309
540
|
return;
|
|
310
541
|
}
|
|
@@ -341,7 +572,7 @@ function buildDescriptorAlt(resolved) {
|
|
|
341
572
|
warnings.forEach((w) => console.warn(w));
|
|
342
573
|
const locales = invocation.locales;
|
|
343
574
|
const defaultLocale = invocation.defaultLocale;
|
|
344
|
-
|
|
575
|
+
const descriptor = {
|
|
345
576
|
models: modelMap,
|
|
346
577
|
roots,
|
|
347
578
|
metadata: locales || defaultLocale
|
|
@@ -351,4 +582,5 @@ function buildDescriptorAlt(resolved) {
|
|
|
351
582
|
}
|
|
352
583
|
: undefined,
|
|
353
584
|
};
|
|
585
|
+
return applyDescriptorOrderingMetadata(descriptor);
|
|
354
586
|
}
|
package/dist/cjs/seo.cjs
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveLocalized = resolveLocalized;
|
|
4
|
+
exports.toOpenGraph = toOpenGraph;
|
|
5
|
+
exports.toTwitter = toTwitter;
|
|
6
|
+
exports.toNextMetadata = toNextMetadata;
|
|
7
|
+
function isRecord(value) {
|
|
8
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
9
|
+
}
|
|
10
|
+
function normalizeText(value) {
|
|
11
|
+
if (typeof value !== "string")
|
|
12
|
+
return undefined;
|
|
13
|
+
const trimmed = value.trim();
|
|
14
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
15
|
+
}
|
|
16
|
+
function uniqueNonEmpty(values) {
|
|
17
|
+
const result = [];
|
|
18
|
+
for (const value of values) {
|
|
19
|
+
const trimmed = normalizeText(value);
|
|
20
|
+
if (!trimmed || result.includes(trimmed))
|
|
21
|
+
continue;
|
|
22
|
+
result.push(trimmed);
|
|
23
|
+
}
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
function isLocalizedLike(value) {
|
|
27
|
+
if (!isRecord(value))
|
|
28
|
+
return false;
|
|
29
|
+
return "locales" in value && isRecord(value.locales);
|
|
30
|
+
}
|
|
31
|
+
function localizedValueForLocale(locales, locale) {
|
|
32
|
+
const entry = locales[locale];
|
|
33
|
+
return normalizeText(entry);
|
|
34
|
+
}
|
|
35
|
+
function resolveLocalized(value, options = {}) {
|
|
36
|
+
if (value == null)
|
|
37
|
+
return undefined;
|
|
38
|
+
if (typeof value === "string") {
|
|
39
|
+
return normalizeText(value);
|
|
40
|
+
}
|
|
41
|
+
if (!isLocalizedLike(value)) {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
const locales = value.locales;
|
|
45
|
+
const localeKeys = Object.keys(locales);
|
|
46
|
+
const preferredLocales = uniqueNonEmpty([
|
|
47
|
+
options.locale,
|
|
48
|
+
typeof value.defaultLocale === "string" ? value.defaultLocale : undefined,
|
|
49
|
+
options.defaultLocale,
|
|
50
|
+
options.fallbackLocale,
|
|
51
|
+
...localeKeys,
|
|
52
|
+
]);
|
|
53
|
+
for (const locale of preferredLocales) {
|
|
54
|
+
const resolved = localizedValueForLocale(locales, locale);
|
|
55
|
+
if (resolved)
|
|
56
|
+
return resolved;
|
|
57
|
+
}
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
function resolveKeywords(value, options) {
|
|
61
|
+
if (Array.isArray(value)) {
|
|
62
|
+
const list = value.map((entry) => normalizeText(entry)).filter(Boolean);
|
|
63
|
+
return list.length ? list : undefined;
|
|
64
|
+
}
|
|
65
|
+
const localized = resolveLocalized(value, options);
|
|
66
|
+
if (!localized)
|
|
67
|
+
return undefined;
|
|
68
|
+
const parts = localized
|
|
69
|
+
.split(/[;,]/g)
|
|
70
|
+
.map((entry) => entry.trim())
|
|
71
|
+
.filter((entry) => entry.length > 0);
|
|
72
|
+
if (parts.length > 0) {
|
|
73
|
+
return parts;
|
|
74
|
+
}
|
|
75
|
+
return [localized];
|
|
76
|
+
}
|
|
77
|
+
function resolveImageUrl(image) {
|
|
78
|
+
if (typeof image === "string") {
|
|
79
|
+
return normalizeText(image);
|
|
80
|
+
}
|
|
81
|
+
if (!isRecord(image))
|
|
82
|
+
return undefined;
|
|
83
|
+
return normalizeText(image.url);
|
|
84
|
+
}
|
|
85
|
+
function normalizeRobots(robots) {
|
|
86
|
+
if (typeof robots === "boolean") {
|
|
87
|
+
return { index: robots, follow: robots };
|
|
88
|
+
}
|
|
89
|
+
if (!isRecord(robots))
|
|
90
|
+
return undefined;
|
|
91
|
+
const resolved = {};
|
|
92
|
+
if (typeof robots.index === "boolean")
|
|
93
|
+
resolved.index = robots.index;
|
|
94
|
+
if (typeof robots.follow === "boolean")
|
|
95
|
+
resolved.follow = robots.follow;
|
|
96
|
+
if (typeof robots.nocache === "boolean")
|
|
97
|
+
resolved.nocache = robots.nocache;
|
|
98
|
+
return Object.keys(resolved).length ? resolved : undefined;
|
|
99
|
+
}
|
|
100
|
+
function hasAnyKeys(value) {
|
|
101
|
+
return Object.keys(value).length > 0;
|
|
102
|
+
}
|
|
103
|
+
function toOpenGraph(value, options = {}) {
|
|
104
|
+
if (!isRecord(value))
|
|
105
|
+
return undefined;
|
|
106
|
+
const openGraph = {};
|
|
107
|
+
const type = normalizeText(value.type);
|
|
108
|
+
if (type)
|
|
109
|
+
openGraph.type = type;
|
|
110
|
+
const url = normalizeText(value.url);
|
|
111
|
+
if (url)
|
|
112
|
+
openGraph.url = url;
|
|
113
|
+
const siteName = normalizeText(value.siteName);
|
|
114
|
+
if (siteName)
|
|
115
|
+
openGraph.siteName = siteName;
|
|
116
|
+
const title = resolveLocalized(value.title, options);
|
|
117
|
+
if (title)
|
|
118
|
+
openGraph.title = title;
|
|
119
|
+
const description = resolveLocalized(value.description, options);
|
|
120
|
+
if (description)
|
|
121
|
+
openGraph.description = description;
|
|
122
|
+
if (Array.isArray(value.images)) {
|
|
123
|
+
const images = value.images
|
|
124
|
+
.map((image) => resolveImageUrl(image))
|
|
125
|
+
.filter((entry) => typeof entry === "string" && entry.length > 0);
|
|
126
|
+
if (images.length) {
|
|
127
|
+
openGraph.images = images;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const locale = normalizeText(value.locale);
|
|
131
|
+
if (locale)
|
|
132
|
+
openGraph.locale = locale;
|
|
133
|
+
if (Array.isArray(value.alternateLocale)) {
|
|
134
|
+
const alternateLocale = value.alternateLocale
|
|
135
|
+
.map((entry) => normalizeText(entry))
|
|
136
|
+
.filter((entry) => Boolean(entry));
|
|
137
|
+
if (alternateLocale.length) {
|
|
138
|
+
openGraph.alternateLocale = alternateLocale;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return hasAnyKeys(openGraph) ? openGraph : undefined;
|
|
142
|
+
}
|
|
143
|
+
function toTwitter(value, options = {}) {
|
|
144
|
+
if (!isRecord(value))
|
|
145
|
+
return undefined;
|
|
146
|
+
const twitter = {};
|
|
147
|
+
const card = normalizeText(value.card);
|
|
148
|
+
if (card)
|
|
149
|
+
twitter.card = card;
|
|
150
|
+
const site = normalizeText(value.site);
|
|
151
|
+
if (site)
|
|
152
|
+
twitter.site = site;
|
|
153
|
+
const creator = normalizeText(value.creator);
|
|
154
|
+
if (creator)
|
|
155
|
+
twitter.creator = creator;
|
|
156
|
+
const title = resolveLocalized(value.title, options);
|
|
157
|
+
if (title)
|
|
158
|
+
twitter.title = title;
|
|
159
|
+
const description = resolveLocalized(value.description, options);
|
|
160
|
+
if (description)
|
|
161
|
+
twitter.description = description;
|
|
162
|
+
if (Array.isArray(value.images)) {
|
|
163
|
+
const images = value.images
|
|
164
|
+
.map((image) => resolveImageUrl(image))
|
|
165
|
+
.filter((entry) => typeof entry === "string" && entry.length > 0);
|
|
166
|
+
if (images.length) {
|
|
167
|
+
twitter.images = images;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return hasAnyKeys(twitter) ? twitter : undefined;
|
|
171
|
+
}
|
|
172
|
+
function resolveAlternates(seo) {
|
|
173
|
+
const alternates = {};
|
|
174
|
+
const canonical = normalizeText(seo.alternates?.canonical) ?? normalizeText(seo.canonical);
|
|
175
|
+
if (canonical) {
|
|
176
|
+
alternates.canonical = canonical;
|
|
177
|
+
}
|
|
178
|
+
if (isRecord(seo.alternates?.languages)) {
|
|
179
|
+
const languages = {};
|
|
180
|
+
for (const [locale, rawValue] of Object.entries(seo.alternates.languages)) {
|
|
181
|
+
const normalizedLocale = normalizeText(locale);
|
|
182
|
+
const normalizedUrl = normalizeText(rawValue);
|
|
183
|
+
if (!normalizedLocale || !normalizedUrl)
|
|
184
|
+
continue;
|
|
185
|
+
languages[normalizedLocale] = normalizedUrl;
|
|
186
|
+
}
|
|
187
|
+
if (Object.keys(languages).length) {
|
|
188
|
+
alternates.languages = languages;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return hasAnyKeys(alternates) ? alternates : undefined;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Maps a CMS0 `Seo` object to a Next.js-compatible metadata shape.
|
|
195
|
+
*
|
|
196
|
+
* The return type is generic so consumers can pass Next's `Metadata` type:
|
|
197
|
+
* `toNextMetadata<Metadata>(seo, { locale })`.
|
|
198
|
+
*/
|
|
199
|
+
function toNextMetadata(seo, options = {}) {
|
|
200
|
+
if (!isRecord(seo)) {
|
|
201
|
+
return {};
|
|
202
|
+
}
|
|
203
|
+
const metadata = {};
|
|
204
|
+
const title = resolveLocalized(seo.title, options);
|
|
205
|
+
if (title)
|
|
206
|
+
metadata.title = title;
|
|
207
|
+
const description = resolveLocalized(seo.description, options);
|
|
208
|
+
if (description)
|
|
209
|
+
metadata.description = description;
|
|
210
|
+
const keywords = resolveKeywords(seo.keywords, options);
|
|
211
|
+
if (keywords?.length)
|
|
212
|
+
metadata.keywords = keywords;
|
|
213
|
+
const robots = normalizeRobots(seo.robots);
|
|
214
|
+
if (robots)
|
|
215
|
+
metadata.robots = robots;
|
|
216
|
+
const alternates = resolveAlternates(seo);
|
|
217
|
+
if (alternates)
|
|
218
|
+
metadata.alternates = alternates;
|
|
219
|
+
const openGraph = toOpenGraph(seo.openGraph, options);
|
|
220
|
+
if (openGraph)
|
|
221
|
+
metadata.openGraph = openGraph;
|
|
222
|
+
const twitter = toTwitter(seo.twitter, options);
|
|
223
|
+
if (twitter)
|
|
224
|
+
metadata.twitter = twitter;
|
|
225
|
+
return metadata;
|
|
226
|
+
}
|