@cms0/cms0 0.2.7 → 0.2.8
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 +10 -3
- package/dist/cjs/custom-types/registry.cjs +250 -5
- package/dist/cjs/index.cjs +194 -15
- 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 +190 -16
- 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 +21 -3
- 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"}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Cms0APIConfig } from "@cms0/cms0/config";
|
|
2
2
|
import { schemaDescriptor } from "@cms0/cms0/schema-descriptors";
|
|
3
3
|
import { FullDescriptor } from "@cms0/shared";
|
|
4
|
-
import type { LocalizedRichText as CmsLocalizedRichText, LocalizedString as CmsLocalizedString, RichText as CmsRichText } from "./custom-types/index.js";
|
|
4
|
+
import type { LocalizedRichText as CmsLocalizedRichText, Seo as CmsSeo, LocalizedString as CmsLocalizedString, RichText as CmsRichText } from "./custom-types/index.js";
|
|
5
5
|
type QueryValue = string | number | boolean | null | undefined;
|
|
6
6
|
type QueryParam = QueryValue | QueryValue[];
|
|
7
7
|
export type CmsQuery = Record<string, QueryParam>;
|
|
@@ -94,7 +94,9 @@ export interface Cms0Options {
|
|
|
94
94
|
includeId?: boolean;
|
|
95
95
|
}
|
|
96
96
|
type PrimitiveValueFromDescriptor<TypeName extends string> = TypeName extends "string" ? string : TypeName extends "number" ? number : TypeName extends "boolean" ? boolean : any;
|
|
97
|
-
type
|
|
97
|
+
type EnumValueTypeFromDescriptor<TypeName extends string> = TypeName extends "string" ? string : TypeName extends "number" ? number : TypeName extends "boolean" ? boolean : never;
|
|
98
|
+
type EnumLiteralFromDescriptor<ValueType extends string, Values> = Values extends readonly (infer Literal)[] ? Literal extends EnumValueTypeFromDescriptor<ValueType> ? Literal : EnumValueTypeFromDescriptor<ValueType> : EnumValueTypeFromDescriptor<ValueType>;
|
|
99
|
+
type CustomTypeValueFromDescriptor<TypeName extends string> = TypeName extends "RichText" ? CmsRichText : TypeName extends "LocalizedString" ? CmsLocalizedString : TypeName extends "LocalizedRichText" ? CmsLocalizedRichText : TypeName extends "Seo" ? CmsSeo : never;
|
|
98
100
|
type ApplyDescriptorFlags<Descriptor, Value> = Descriptor extends {
|
|
99
101
|
optional: true;
|
|
100
102
|
nullable: true;
|
|
@@ -106,6 +108,7 @@ type ApplyDescriptorFlags<Descriptor, Value> = Descriptor extends {
|
|
|
106
108
|
type InferDescriptorObject<Properties extends Record<string, any>, ModelMap extends Record<string, any>> = {
|
|
107
109
|
[Key in keyof Properties]: InferDescriptorField<Properties[Key], ModelMap>;
|
|
108
110
|
};
|
|
111
|
+
type InferUnionBranches<Branches, ModelMap extends Record<string, any>> = Branches extends readonly (infer Branch)[] ? InferDescriptorField<Branch, ModelMap> : any;
|
|
109
112
|
type InferDescriptorField<Descriptor, ModelMap extends Record<string, any>> = ApplyDescriptorFlags<Descriptor, Descriptor extends {
|
|
110
113
|
customType: infer CustomType extends string;
|
|
111
114
|
} ? CustomTypeValueFromDescriptor<CustomType> extends never ? Descriptor extends {
|
|
@@ -115,6 +118,13 @@ type InferDescriptorField<Descriptor, ModelMap extends Record<string, any>> = Ap
|
|
|
115
118
|
kind: "primitive";
|
|
116
119
|
type: infer Primitive extends string;
|
|
117
120
|
} ? PrimitiveValueFromDescriptor<Primitive> : Descriptor extends {
|
|
121
|
+
kind: "enum";
|
|
122
|
+
valueType: infer ValueType extends string;
|
|
123
|
+
values: infer Values;
|
|
124
|
+
} ? EnumLiteralFromDescriptor<ValueType, Values> : Descriptor extends {
|
|
125
|
+
kind: "union";
|
|
126
|
+
anyOf: infer Branches;
|
|
127
|
+
} ? InferUnionBranches<Branches, ModelMap> : Descriptor extends {
|
|
118
128
|
type: "array";
|
|
119
129
|
items: infer Items;
|
|
120
130
|
} ? Array<InferDescriptorField<Items, ModelMap>> : Descriptor extends {
|
|
@@ -129,6 +139,13 @@ type InferDescriptorField<Descriptor, ModelMap extends Record<string, any>> = Ap
|
|
|
129
139
|
kind: "primitive";
|
|
130
140
|
type: infer Primitive extends string;
|
|
131
141
|
} ? PrimitiveValueFromDescriptor<Primitive> : Descriptor extends {
|
|
142
|
+
kind: "enum";
|
|
143
|
+
valueType: infer ValueType extends string;
|
|
144
|
+
values: infer Values;
|
|
145
|
+
} ? EnumLiteralFromDescriptor<ValueType, Values> : Descriptor extends {
|
|
146
|
+
kind: "union";
|
|
147
|
+
anyOf: infer Branches;
|
|
148
|
+
} ? InferUnionBranches<Branches, ModelMap> : Descriptor extends {
|
|
132
149
|
type: "array";
|
|
133
150
|
items: infer Items;
|
|
134
151
|
} ? Array<InferDescriptorField<Items, ModelMap>> : Descriptor extends {
|
|
@@ -174,5 +191,6 @@ export declare function cms0<Roots = GeneratedRoots, Models extends Record<strin
|
|
|
174
191
|
includeId?: false | undefined;
|
|
175
192
|
}): CmsInstance<Roots, Models, false>;
|
|
176
193
|
export declare function cms0<Roots = GeneratedRoots, Models extends Record<string, any> = GeneratedModels>(config: Cms0Options): CmsInstance<Roots, Models, boolean>;
|
|
177
|
-
export {};
|
|
194
|
+
export { resolveLocalized, toNextMetadata, toOpenGraph, toTwitter, } from "./seo.js";
|
|
195
|
+
export type { ResolveLocalizedOptions, ToNextMetadataOptions, } from "./seo.js";
|
|
178
196
|
//# sourceMappingURL=index.d.ts.map
|