@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
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { Project, SyntaxKind, Node, } from "ts-morph";
|
|
5
|
+
import { computeUnionBranchKeys, } from "@cms0/shared";
|
|
5
6
|
import { findTsConfig } from "./config-loader.js";
|
|
6
7
|
import { customTypeNames, customInlineDescriptors, customInlineTypeNames, customModelDescriptors, customModelTypeNames, resolveCustomTypeDependencies, } from "../../custom-types/registry.js";
|
|
7
8
|
function resolveCustomTypeName(type) {
|
|
@@ -121,6 +122,39 @@ function descriptorFromType(type, modelNames, warnings, ctx, opts) {
|
|
|
121
122
|
const { base, optional, nullable } = unwrapOptional(type);
|
|
122
123
|
const isOptional = !!opts?.optional || optional;
|
|
123
124
|
const isNullable = !!opts?.nullable || nullable;
|
|
125
|
+
if (base.isUnion()) {
|
|
126
|
+
const unionDescriptor = descriptorFromUnionType(base, modelNames, warnings, ctx);
|
|
127
|
+
if (unionDescriptor) {
|
|
128
|
+
return applyOptionalityToDescriptor(unionDescriptor, isOptional, isNullable);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (base.isStringLiteral()) {
|
|
132
|
+
return {
|
|
133
|
+
kind: "enum",
|
|
134
|
+
valueType: "string",
|
|
135
|
+
values: [String(base.getLiteralValue())],
|
|
136
|
+
optional: isOptional,
|
|
137
|
+
nullable: isNullable,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
if (base.isNumberLiteral()) {
|
|
141
|
+
return {
|
|
142
|
+
kind: "enum",
|
|
143
|
+
valueType: "number",
|
|
144
|
+
values: [Number(base.getLiteralValue())],
|
|
145
|
+
optional: isOptional,
|
|
146
|
+
nullable: isNullable,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
if (base.isBooleanLiteral()) {
|
|
150
|
+
return {
|
|
151
|
+
kind: "enum",
|
|
152
|
+
valueType: "boolean",
|
|
153
|
+
values: [base.getText() === "true"],
|
|
154
|
+
optional: isOptional,
|
|
155
|
+
nullable: isNullable,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
124
158
|
if (base.isString()) {
|
|
125
159
|
return {
|
|
126
160
|
kind: "primitive",
|
|
@@ -189,6 +223,203 @@ function descriptorFromType(type, modelNames, warnings, ctx, opts) {
|
|
|
189
223
|
nullable: isNullable,
|
|
190
224
|
};
|
|
191
225
|
}
|
|
226
|
+
function descriptorFromUnionType(unionType, modelNames, warnings, ctx) {
|
|
227
|
+
const unionTypes = unionType.getUnionTypes();
|
|
228
|
+
if (!unionTypes.length)
|
|
229
|
+
return undefined;
|
|
230
|
+
const hasBroadString = unionTypes.some((entry) => entry.isString());
|
|
231
|
+
const hasBroadNumber = unionTypes.some((entry) => entry.isNumber());
|
|
232
|
+
const hasBroadBoolean = unionTypes.some((entry) => entry.isBoolean());
|
|
233
|
+
const filtered = unionTypes.filter((entry) => {
|
|
234
|
+
if (entry.isStringLiteral() && hasBroadString)
|
|
235
|
+
return false;
|
|
236
|
+
if (entry.isNumberLiteral() && hasBroadNumber)
|
|
237
|
+
return false;
|
|
238
|
+
if (entry.isBooleanLiteral() && hasBroadBoolean)
|
|
239
|
+
return false;
|
|
240
|
+
return true;
|
|
241
|
+
});
|
|
242
|
+
if (!filtered.length) {
|
|
243
|
+
if (hasBroadString) {
|
|
244
|
+
return { kind: "primitive", type: "string" };
|
|
245
|
+
}
|
|
246
|
+
if (hasBroadNumber) {
|
|
247
|
+
return { kind: "primitive", type: "number" };
|
|
248
|
+
}
|
|
249
|
+
if (hasBroadBoolean) {
|
|
250
|
+
return { kind: "primitive", type: "boolean" };
|
|
251
|
+
}
|
|
252
|
+
return undefined;
|
|
253
|
+
}
|
|
254
|
+
const literalEnum = resolveLiteralUnionAsEnum(filtered);
|
|
255
|
+
if (literalEnum) {
|
|
256
|
+
return literalEnum;
|
|
257
|
+
}
|
|
258
|
+
const branches = filtered
|
|
259
|
+
.map((entry, index) => descriptorFromType(entry, modelNames, warnings, `${ctx}|${index}`, {
|
|
260
|
+
optional: false,
|
|
261
|
+
nullable: false,
|
|
262
|
+
}))
|
|
263
|
+
.map((entry) => stripOptionality(entry));
|
|
264
|
+
const deduped = dedupeDescriptors(branches);
|
|
265
|
+
if (!deduped.length)
|
|
266
|
+
return undefined;
|
|
267
|
+
if (deduped.length === 1)
|
|
268
|
+
return deduped[0];
|
|
269
|
+
const discriminator = inferUnionDiscriminator(deduped);
|
|
270
|
+
const branchKeys = computeUnionBranchKeys(deduped);
|
|
271
|
+
return {
|
|
272
|
+
kind: "union",
|
|
273
|
+
anyOf: deduped,
|
|
274
|
+
branchKeys,
|
|
275
|
+
...(discriminator ? { discriminator: { key: discriminator } } : {}),
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
function stripOptionality(descriptor) {
|
|
279
|
+
const clone = JSON.parse(JSON.stringify(descriptor));
|
|
280
|
+
delete clone.optional;
|
|
281
|
+
delete clone.nullable;
|
|
282
|
+
return clone;
|
|
283
|
+
}
|
|
284
|
+
function dedupeDescriptors(descriptors) {
|
|
285
|
+
const seen = new Set();
|
|
286
|
+
const deduped = [];
|
|
287
|
+
for (const descriptor of descriptors) {
|
|
288
|
+
const key = JSON.stringify(descriptor);
|
|
289
|
+
if (seen.has(key))
|
|
290
|
+
continue;
|
|
291
|
+
seen.add(key);
|
|
292
|
+
deduped.push(descriptor);
|
|
293
|
+
}
|
|
294
|
+
return deduped;
|
|
295
|
+
}
|
|
296
|
+
function resolveLiteralUnionAsEnum(types) {
|
|
297
|
+
if (!types.length)
|
|
298
|
+
return undefined;
|
|
299
|
+
const literals = [];
|
|
300
|
+
let kind;
|
|
301
|
+
for (const entry of types) {
|
|
302
|
+
if (entry.isStringLiteral()) {
|
|
303
|
+
if (kind && kind !== "string")
|
|
304
|
+
return undefined;
|
|
305
|
+
kind = "string";
|
|
306
|
+
literals.push(String(entry.getLiteralValue()));
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
if (entry.isNumberLiteral()) {
|
|
310
|
+
if (kind && kind !== "number")
|
|
311
|
+
return undefined;
|
|
312
|
+
kind = "number";
|
|
313
|
+
literals.push(Number(entry.getLiteralValue()));
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
if (entry.isBooleanLiteral()) {
|
|
317
|
+
if (kind && kind !== "boolean")
|
|
318
|
+
return undefined;
|
|
319
|
+
kind = "boolean";
|
|
320
|
+
literals.push(entry.getText() === "true");
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
return undefined;
|
|
324
|
+
}
|
|
325
|
+
if (!kind)
|
|
326
|
+
return undefined;
|
|
327
|
+
const unique = Array.from(new Set(literals));
|
|
328
|
+
return {
|
|
329
|
+
kind: "enum",
|
|
330
|
+
valueType: kind,
|
|
331
|
+
values: unique,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
function inferUnionDiscriminator(descriptors) {
|
|
335
|
+
if (!descriptors.length ||
|
|
336
|
+
descriptors.some((entry) => entry?.type !== "object")) {
|
|
337
|
+
return undefined;
|
|
338
|
+
}
|
|
339
|
+
const objects = descriptors;
|
|
340
|
+
const sharedKeys = Object.keys(objects[0]?.properties ?? {}).filter((key) => objects.every((obj) => Object.prototype.hasOwnProperty.call(obj.properties ?? {}, key)));
|
|
341
|
+
for (const key of sharedKeys) {
|
|
342
|
+
const enumLikeValues = objects.map((obj) => {
|
|
343
|
+
const prop = (obj.properties ?? {})[key];
|
|
344
|
+
if (!prop)
|
|
345
|
+
return undefined;
|
|
346
|
+
if (prop.kind !== "enum" || !Array.isArray(prop.values) || prop.values.length !== 1) {
|
|
347
|
+
return undefined;
|
|
348
|
+
}
|
|
349
|
+
return prop.values[0];
|
|
350
|
+
});
|
|
351
|
+
if (enumLikeValues.some((value) => value === undefined)) {
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
const unique = new Set(enumLikeValues);
|
|
355
|
+
if (unique.size === enumLikeValues.length) {
|
|
356
|
+
return key;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return undefined;
|
|
360
|
+
}
|
|
361
|
+
function normalizePropertyOrder(keys, explicitOrder) {
|
|
362
|
+
const normalizedExplicit = Array.isArray(explicitOrder)
|
|
363
|
+
? explicitOrder
|
|
364
|
+
.filter((value) => typeof value === "string")
|
|
365
|
+
.map((value) => value.trim())
|
|
366
|
+
.filter(Boolean)
|
|
367
|
+
: [];
|
|
368
|
+
const dedupedExplicit = Array.from(new Set(normalizedExplicit)).filter((key) => keys.includes(key));
|
|
369
|
+
const remaining = keys.filter((key) => !dedupedExplicit.includes(key));
|
|
370
|
+
return [...dedupedExplicit, ...remaining];
|
|
371
|
+
}
|
|
372
|
+
function applyFieldPropertyOrder(descriptor) {
|
|
373
|
+
if (!descriptor || typeof descriptor !== "object")
|
|
374
|
+
return descriptor;
|
|
375
|
+
if (descriptor.type === "object") {
|
|
376
|
+
const properties = (descriptor.properties ?? {});
|
|
377
|
+
const keys = Object.keys(properties);
|
|
378
|
+
for (const key of keys) {
|
|
379
|
+
properties[key] = applyFieldPropertyOrder(properties[key]);
|
|
380
|
+
}
|
|
381
|
+
descriptor.propertyOrder = normalizePropertyOrder(keys, descriptor.propertyOrder);
|
|
382
|
+
return descriptor;
|
|
383
|
+
}
|
|
384
|
+
if (descriptor.type === "array" && descriptor.items) {
|
|
385
|
+
descriptor.items = applyFieldPropertyOrder(descriptor.items);
|
|
386
|
+
return descriptor;
|
|
387
|
+
}
|
|
388
|
+
if (descriptor.kind === "union" && Array.isArray(descriptor.anyOf)) {
|
|
389
|
+
descriptor.anyOf = descriptor.anyOf.map((branch) => applyFieldPropertyOrder(branch));
|
|
390
|
+
return descriptor;
|
|
391
|
+
}
|
|
392
|
+
return descriptor;
|
|
393
|
+
}
|
|
394
|
+
function applyDescriptorOrderingMetadata(descriptor) {
|
|
395
|
+
const rootsOrder = Object.keys(descriptor.roots ?? {});
|
|
396
|
+
const modelsOrder = Object.keys(descriptor.models ?? {});
|
|
397
|
+
for (const modelName of modelsOrder) {
|
|
398
|
+
const model = descriptor.models?.[modelName];
|
|
399
|
+
if (!model || typeof model !== "object")
|
|
400
|
+
continue;
|
|
401
|
+
const keys = Object.keys(model.properties ?? {});
|
|
402
|
+
for (const key of keys) {
|
|
403
|
+
model.properties[key] = applyFieldPropertyOrder(model.properties[key]);
|
|
404
|
+
}
|
|
405
|
+
model.propertyOrder = normalizePropertyOrder(keys, model.propertyOrder);
|
|
406
|
+
}
|
|
407
|
+
for (const rootName of rootsOrder) {
|
|
408
|
+
const rootDescriptor = descriptor.roots?.[rootName];
|
|
409
|
+
if (!rootDescriptor)
|
|
410
|
+
continue;
|
|
411
|
+
descriptor.roots[rootName] = applyFieldPropertyOrder(rootDescriptor);
|
|
412
|
+
}
|
|
413
|
+
descriptor.metadata = {
|
|
414
|
+
...(descriptor.metadata ?? {}),
|
|
415
|
+
ordering: {
|
|
416
|
+
...(descriptor.metadata?.ordering ?? {}),
|
|
417
|
+
roots: rootsOrder,
|
|
418
|
+
models: modelsOrder,
|
|
419
|
+
},
|
|
420
|
+
};
|
|
421
|
+
return descriptor;
|
|
422
|
+
}
|
|
192
423
|
function collectModels(sourceFiles, modelNames, modelMap, warnings) {
|
|
193
424
|
const handleShape = (name, type, ctx) => {
|
|
194
425
|
if (modelMap[name])
|
|
@@ -290,15 +521,15 @@ function buildDescriptorAlt(resolved) {
|
|
|
290
521
|
const referencedModelNames = new Set();
|
|
291
522
|
collectReferencedModelNames(rootType, referencedModelNames, new Set(), "root");
|
|
292
523
|
const referencedCustomTypeNames = new Set(Array.from(referencedModelNames).filter((n) => customTypeNames.has(n)));
|
|
293
|
-
const
|
|
294
|
-
const
|
|
524
|
+
const resolvedCustomTypeNames = resolveCustomTypeDependencies(referencedCustomTypeNames);
|
|
525
|
+
const resolvedCustomModelNames = new Set(Array.from(resolvedCustomTypeNames).filter((n) => customModelTypeNames.has(n)));
|
|
295
526
|
const modelNames = new Set([
|
|
296
527
|
...Array.from(referencedModelNames).filter((n) => exportedModelNames.has(n)),
|
|
297
|
-
...Array.from(
|
|
528
|
+
...Array.from(resolvedCustomModelNames),
|
|
298
529
|
]);
|
|
299
530
|
const modelMap = {};
|
|
300
531
|
collectModels(sourceFiles, modelNames, modelMap, warnings);
|
|
301
|
-
|
|
532
|
+
resolvedCustomModelNames.forEach((name) => {
|
|
302
533
|
if (modelMap[name]) {
|
|
303
534
|
return;
|
|
304
535
|
}
|
|
@@ -335,7 +566,7 @@ function buildDescriptorAlt(resolved) {
|
|
|
335
566
|
warnings.forEach((w) => console.warn(w));
|
|
336
567
|
const locales = invocation.locales;
|
|
337
568
|
const defaultLocale = invocation.defaultLocale;
|
|
338
|
-
|
|
569
|
+
const descriptor = {
|
|
339
570
|
models: modelMap,
|
|
340
571
|
roots,
|
|
341
572
|
metadata: locales || defaultLocale
|
|
@@ -345,5 +576,6 @@ function buildDescriptorAlt(resolved) {
|
|
|
345
576
|
}
|
|
346
577
|
: undefined,
|
|
347
578
|
};
|
|
579
|
+
return applyDescriptorOrderingMetadata(descriptor);
|
|
348
580
|
}
|
|
349
581
|
export { buildDescriptorAlt };
|
package/dist/esm/seo.js
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
function isRecord(value) {
|
|
2
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
3
|
+
}
|
|
4
|
+
function normalizeText(value) {
|
|
5
|
+
if (typeof value !== "string")
|
|
6
|
+
return undefined;
|
|
7
|
+
const trimmed = value.trim();
|
|
8
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
9
|
+
}
|
|
10
|
+
function uniqueNonEmpty(values) {
|
|
11
|
+
const result = [];
|
|
12
|
+
for (const value of values) {
|
|
13
|
+
const trimmed = normalizeText(value);
|
|
14
|
+
if (!trimmed || result.includes(trimmed))
|
|
15
|
+
continue;
|
|
16
|
+
result.push(trimmed);
|
|
17
|
+
}
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
function isLocalizedLike(value) {
|
|
21
|
+
if (!isRecord(value))
|
|
22
|
+
return false;
|
|
23
|
+
return "locales" in value && isRecord(value.locales);
|
|
24
|
+
}
|
|
25
|
+
function localizedValueForLocale(locales, locale) {
|
|
26
|
+
const entry = locales[locale];
|
|
27
|
+
return normalizeText(entry);
|
|
28
|
+
}
|
|
29
|
+
export function resolveLocalized(value, options = {}) {
|
|
30
|
+
if (value == null)
|
|
31
|
+
return undefined;
|
|
32
|
+
if (typeof value === "string") {
|
|
33
|
+
return normalizeText(value);
|
|
34
|
+
}
|
|
35
|
+
if (!isLocalizedLike(value)) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
const locales = value.locales;
|
|
39
|
+
const localeKeys = Object.keys(locales);
|
|
40
|
+
const preferredLocales = uniqueNonEmpty([
|
|
41
|
+
options.locale,
|
|
42
|
+
typeof value.defaultLocale === "string" ? value.defaultLocale : undefined,
|
|
43
|
+
options.defaultLocale,
|
|
44
|
+
options.fallbackLocale,
|
|
45
|
+
...localeKeys,
|
|
46
|
+
]);
|
|
47
|
+
for (const locale of preferredLocales) {
|
|
48
|
+
const resolved = localizedValueForLocale(locales, locale);
|
|
49
|
+
if (resolved)
|
|
50
|
+
return resolved;
|
|
51
|
+
}
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
function resolveKeywords(value, options) {
|
|
55
|
+
if (Array.isArray(value)) {
|
|
56
|
+
const list = value.map((entry) => normalizeText(entry)).filter(Boolean);
|
|
57
|
+
return list.length ? list : undefined;
|
|
58
|
+
}
|
|
59
|
+
const localized = resolveLocalized(value, options);
|
|
60
|
+
if (!localized)
|
|
61
|
+
return undefined;
|
|
62
|
+
const parts = localized
|
|
63
|
+
.split(/[;,]/g)
|
|
64
|
+
.map((entry) => entry.trim())
|
|
65
|
+
.filter((entry) => entry.length > 0);
|
|
66
|
+
if (parts.length > 0) {
|
|
67
|
+
return parts;
|
|
68
|
+
}
|
|
69
|
+
return [localized];
|
|
70
|
+
}
|
|
71
|
+
function resolveImageUrl(image) {
|
|
72
|
+
if (typeof image === "string") {
|
|
73
|
+
return normalizeText(image);
|
|
74
|
+
}
|
|
75
|
+
if (!isRecord(image))
|
|
76
|
+
return undefined;
|
|
77
|
+
return normalizeText(image.url);
|
|
78
|
+
}
|
|
79
|
+
function normalizeRobots(robots) {
|
|
80
|
+
if (typeof robots === "boolean") {
|
|
81
|
+
return { index: robots, follow: robots };
|
|
82
|
+
}
|
|
83
|
+
if (!isRecord(robots))
|
|
84
|
+
return undefined;
|
|
85
|
+
const resolved = {};
|
|
86
|
+
if (typeof robots.index === "boolean")
|
|
87
|
+
resolved.index = robots.index;
|
|
88
|
+
if (typeof robots.follow === "boolean")
|
|
89
|
+
resolved.follow = robots.follow;
|
|
90
|
+
if (typeof robots.nocache === "boolean")
|
|
91
|
+
resolved.nocache = robots.nocache;
|
|
92
|
+
return Object.keys(resolved).length ? resolved : undefined;
|
|
93
|
+
}
|
|
94
|
+
function hasAnyKeys(value) {
|
|
95
|
+
return Object.keys(value).length > 0;
|
|
96
|
+
}
|
|
97
|
+
export function toOpenGraph(value, options = {}) {
|
|
98
|
+
if (!isRecord(value))
|
|
99
|
+
return undefined;
|
|
100
|
+
const openGraph = {};
|
|
101
|
+
const type = normalizeText(value.type);
|
|
102
|
+
if (type)
|
|
103
|
+
openGraph.type = type;
|
|
104
|
+
const url = normalizeText(value.url);
|
|
105
|
+
if (url)
|
|
106
|
+
openGraph.url = url;
|
|
107
|
+
const siteName = normalizeText(value.siteName);
|
|
108
|
+
if (siteName)
|
|
109
|
+
openGraph.siteName = siteName;
|
|
110
|
+
const title = resolveLocalized(value.title, options);
|
|
111
|
+
if (title)
|
|
112
|
+
openGraph.title = title;
|
|
113
|
+
const description = resolveLocalized(value.description, options);
|
|
114
|
+
if (description)
|
|
115
|
+
openGraph.description = description;
|
|
116
|
+
if (Array.isArray(value.images)) {
|
|
117
|
+
const images = value.images
|
|
118
|
+
.map((image) => resolveImageUrl(image))
|
|
119
|
+
.filter((entry) => typeof entry === "string" && entry.length > 0);
|
|
120
|
+
if (images.length) {
|
|
121
|
+
openGraph.images = images;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const locale = normalizeText(value.locale);
|
|
125
|
+
if (locale)
|
|
126
|
+
openGraph.locale = locale;
|
|
127
|
+
if (Array.isArray(value.alternateLocale)) {
|
|
128
|
+
const alternateLocale = value.alternateLocale
|
|
129
|
+
.map((entry) => normalizeText(entry))
|
|
130
|
+
.filter((entry) => Boolean(entry));
|
|
131
|
+
if (alternateLocale.length) {
|
|
132
|
+
openGraph.alternateLocale = alternateLocale;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return hasAnyKeys(openGraph) ? openGraph : undefined;
|
|
136
|
+
}
|
|
137
|
+
export function toTwitter(value, options = {}) {
|
|
138
|
+
if (!isRecord(value))
|
|
139
|
+
return undefined;
|
|
140
|
+
const twitter = {};
|
|
141
|
+
const card = normalizeText(value.card);
|
|
142
|
+
if (card)
|
|
143
|
+
twitter.card = card;
|
|
144
|
+
const site = normalizeText(value.site);
|
|
145
|
+
if (site)
|
|
146
|
+
twitter.site = site;
|
|
147
|
+
const creator = normalizeText(value.creator);
|
|
148
|
+
if (creator)
|
|
149
|
+
twitter.creator = creator;
|
|
150
|
+
const title = resolveLocalized(value.title, options);
|
|
151
|
+
if (title)
|
|
152
|
+
twitter.title = title;
|
|
153
|
+
const description = resolveLocalized(value.description, options);
|
|
154
|
+
if (description)
|
|
155
|
+
twitter.description = description;
|
|
156
|
+
if (Array.isArray(value.images)) {
|
|
157
|
+
const images = value.images
|
|
158
|
+
.map((image) => resolveImageUrl(image))
|
|
159
|
+
.filter((entry) => typeof entry === "string" && entry.length > 0);
|
|
160
|
+
if (images.length) {
|
|
161
|
+
twitter.images = images;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return hasAnyKeys(twitter) ? twitter : undefined;
|
|
165
|
+
}
|
|
166
|
+
function resolveAlternates(seo) {
|
|
167
|
+
const alternates = {};
|
|
168
|
+
const canonical = normalizeText(seo.alternates?.canonical) ?? normalizeText(seo.canonical);
|
|
169
|
+
if (canonical) {
|
|
170
|
+
alternates.canonical = canonical;
|
|
171
|
+
}
|
|
172
|
+
if (isRecord(seo.alternates?.languages)) {
|
|
173
|
+
const languages = {};
|
|
174
|
+
for (const [locale, rawValue] of Object.entries(seo.alternates.languages)) {
|
|
175
|
+
const normalizedLocale = normalizeText(locale);
|
|
176
|
+
const normalizedUrl = normalizeText(rawValue);
|
|
177
|
+
if (!normalizedLocale || !normalizedUrl)
|
|
178
|
+
continue;
|
|
179
|
+
languages[normalizedLocale] = normalizedUrl;
|
|
180
|
+
}
|
|
181
|
+
if (Object.keys(languages).length) {
|
|
182
|
+
alternates.languages = languages;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return hasAnyKeys(alternates) ? alternates : undefined;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Maps a CMS0 `Seo` object to a Next.js-compatible metadata shape.
|
|
189
|
+
*
|
|
190
|
+
* The return type is generic so consumers can pass Next's `Metadata` type:
|
|
191
|
+
* `toNextMetadata<Metadata>(seo, { locale })`.
|
|
192
|
+
*/
|
|
193
|
+
export function toNextMetadata(seo, options = {}) {
|
|
194
|
+
if (!isRecord(seo)) {
|
|
195
|
+
return {};
|
|
196
|
+
}
|
|
197
|
+
const metadata = {};
|
|
198
|
+
const title = resolveLocalized(seo.title, options);
|
|
199
|
+
if (title)
|
|
200
|
+
metadata.title = title;
|
|
201
|
+
const description = resolveLocalized(seo.description, options);
|
|
202
|
+
if (description)
|
|
203
|
+
metadata.description = description;
|
|
204
|
+
const keywords = resolveKeywords(seo.keywords, options);
|
|
205
|
+
if (keywords?.length)
|
|
206
|
+
metadata.keywords = keywords;
|
|
207
|
+
const robots = normalizeRobots(seo.robots);
|
|
208
|
+
if (robots)
|
|
209
|
+
metadata.robots = robots;
|
|
210
|
+
const alternates = resolveAlternates(seo);
|
|
211
|
+
if (alternates)
|
|
212
|
+
metadata.alternates = alternates;
|
|
213
|
+
const openGraph = toOpenGraph(seo.openGraph, options);
|
|
214
|
+
if (openGraph)
|
|
215
|
+
metadata.openGraph = openGraph;
|
|
216
|
+
const twitter = toTwitter(seo.twitter, options);
|
|
217
|
+
if (twitter)
|
|
218
|
+
metadata.twitter = twitter;
|
|
219
|
+
return metadata;
|
|
220
|
+
}
|
|
@@ -28,4 +28,42 @@ export type Localized<T> = {
|
|
|
28
28
|
};
|
|
29
29
|
export type LocalizedString = Localized<string>;
|
|
30
30
|
export type LocalizedRichText = Localized<RichText>;
|
|
31
|
+
export type SeoRobots = boolean | {
|
|
32
|
+
index?: boolean;
|
|
33
|
+
follow?: boolean;
|
|
34
|
+
nocache?: boolean;
|
|
35
|
+
};
|
|
36
|
+
export type SeoAlternates = {
|
|
37
|
+
canonical?: string;
|
|
38
|
+
languages?: Record<string, string>;
|
|
39
|
+
};
|
|
40
|
+
export type SeoOpenGraph = {
|
|
41
|
+
type?: "website" | "article" | "profile";
|
|
42
|
+
url?: string;
|
|
43
|
+
siteName?: string;
|
|
44
|
+
title?: LocalizedString | string;
|
|
45
|
+
description?: LocalizedString | string;
|
|
46
|
+
images?: Array<Image | string>;
|
|
47
|
+
locale?: string;
|
|
48
|
+
alternateLocale?: string[];
|
|
49
|
+
};
|
|
50
|
+
export type SeoTwitter = {
|
|
51
|
+
card?: "summary" | "summary_large_image" | "app" | "player";
|
|
52
|
+
site?: string;
|
|
53
|
+
creator?: string;
|
|
54
|
+
title?: LocalizedString | string;
|
|
55
|
+
description?: LocalizedString | string;
|
|
56
|
+
images?: Array<Image | string>;
|
|
57
|
+
};
|
|
58
|
+
export type Seo = {
|
|
59
|
+
title?: LocalizedString | string;
|
|
60
|
+
description?: LocalizedString | string;
|
|
61
|
+
keywords?: LocalizedString | string[];
|
|
62
|
+
canonical?: string;
|
|
63
|
+
robots?: SeoRobots;
|
|
64
|
+
alternates?: SeoAlternates;
|
|
65
|
+
openGraph?: SeoOpenGraph;
|
|
66
|
+
twitter?: SeoTwitter;
|
|
67
|
+
jsonLd?: Record<string, unknown>[];
|
|
68
|
+
};
|
|
31
69
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/custom-types/index.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,IAAI,GAAG,QAAQ,CAAC;AAE5B,MAAM,MAAM,KAAK,GAAG,QAAQ,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,KAAK,GAAG,QAAQ,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;AAEhD,MAAM,MAAM,iBAAiB,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/custom-types/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type FieldDescriptor, type ModelDescriptor, type PrimitiveType } from "@cms0/shared";
|
|
2
2
|
export declare const customModelDescriptors: {
|
|
3
3
|
readonly File: ModelDescriptor;
|
|
4
4
|
readonly Image: ModelDescriptor;
|
|
@@ -26,6 +26,15 @@ export declare const customInlineDescriptors: {
|
|
|
26
26
|
nullable?: boolean;
|
|
27
27
|
customType?: string;
|
|
28
28
|
};
|
|
29
|
+
readonly Seo: {
|
|
30
|
+
kind?: "object";
|
|
31
|
+
type: "object";
|
|
32
|
+
properties: Record<string, FieldDescriptor>;
|
|
33
|
+
propertyOrder?: string[];
|
|
34
|
+
optional?: boolean;
|
|
35
|
+
nullable?: boolean;
|
|
36
|
+
customType?: string;
|
|
37
|
+
};
|
|
29
38
|
};
|
|
30
39
|
export type CustomInlineTypeName = keyof typeof customInlineDescriptors;
|
|
31
40
|
export type CustomInlineTypeMetadata = {
|
|
@@ -56,6 +65,15 @@ export declare const customTypeDescriptors: {
|
|
|
56
65
|
nullable?: boolean;
|
|
57
66
|
customType?: string;
|
|
58
67
|
};
|
|
68
|
+
readonly Seo: {
|
|
69
|
+
kind?: "object";
|
|
70
|
+
type: "object";
|
|
71
|
+
properties: Record<string, FieldDescriptor>;
|
|
72
|
+
propertyOrder?: string[];
|
|
73
|
+
optional?: boolean;
|
|
74
|
+
nullable?: boolean;
|
|
75
|
+
customType?: string;
|
|
76
|
+
};
|
|
59
77
|
readonly File: ModelDescriptor;
|
|
60
78
|
readonly Image: ModelDescriptor;
|
|
61
79
|
readonly Video: ModelDescriptor;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/custom-types/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/custom-types/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,aAAa,EACnB,MAAM,cAAc,CAAC;AA6StB,eAAO,MAAM,sBAAsB;;;;CAIzB,CAAC;AAEX,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAK1B,CAAC;AAEX,MAAM,MAAM,oBAAoB,GAAG,MAAM,OAAO,uBAAuB,CAAC;AAExE,MAAM,MAAM,wBAAwB,GAAG;IACrC,SAAS,CAAC,EAAE;QAEV,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;KACnC,CAAC;CACH,CAAC;AAEF,eAAO,MAAM,wBAAwB,EAAE,MAAM,CAC3C,oBAAoB,EACpB,wBAAwB,CAUzB,CAAC;AAEF,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAGxB,CAAC;AAEX,MAAM,MAAM,cAAc,GAAG,MAAM,OAAO,qBAAqB,CAAC;AAEhE,eAAO,MAAM,eAAe,aAAsD,CAAC;AACnF,eAAO,MAAM,oBAAoB,aAEhC,CAAC;AACF,eAAO,MAAM,qBAAqB,aAEjC,CAAC;AAEF,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,MAAM,GACX,wBAAwB,GAAG,SAAS,CAEtC;AAED,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CAgClF"}
|