@paroicms/server
Version:
The ParoiCMS server
722 lines • 28.7 kB
JavaScript
import { PART_DEFAULT_LIST_NAME } from "@paroicms/internal-anywhere-lib";
import { ensureLanguagesFormat, jsonTypeValidator, parseSorting, siteSchemaFormatVersion, } from "@paroicms/public-server-lib";
import { type } from "arktype";
import { readFile } from "node:fs/promises";
import { join } from "node:path";
import { defaultImageQualityPolicy, defaultMediaPolicy } from "./default-media-policy.js";
import { deriveOgTypeFromJsonLdType } from "./derive-og-type.js";
import { completeSchemaLocales, createEmptySchemaLocales, importPartOfL10n, makeSiteLanguageLabels, } from "./read-locales.js";
import { prepareLib } from "./schema-lib.js";
import { checkSiteSchemaFormatVersion } from "./site-schema-helpers.js";
const StringAT = type("string");
const authorizedClassicRoute = new Set([
":relativeId-:slug",
":yyyy/:mm/:dd/:relativeId-:slug",
":slug",
":relativeId",
]);
const invalidFieldNames = ["featuredImage", "adminUiFavicon"];
export async function loadSiteSchema({ logger, regSite, commonSchemaLib, pluginStaticConfigurations, trusted, }) {
const jtRead = await readJtSiteSchema(join(regSite.siteDir, "site-schema.json"));
if (!jtRead.upToDate) {
return {
upToDate: false,
siteSchemaFormatVersion: jtRead.siteSchemaFormatVersion,
oldSchema: jtRead.jsonData,
};
}
const { jsonData } = jtRead;
if (jsonData.languages.length === 0)
throw new Error("missing languages in site-schema");
const languages = ensureLanguagesFormat(jsonData.languages);
const lib = await prepareLib({
logger,
regSite,
pluginStaticConfigurations,
commonSchemaLib,
languages,
pluginNamesOrRefs: jsonData.plugins,
});
const l10n = createEmptySchemaLocales(languages, lib.common.languageLabels);
const imageQualityPolicy = jsonData.imageQualityPolicy ?? lib.schema.imageQualityPolicy ?? defaultImageQualityPolicy;
if (!imageQualityPolicy)
throw new Error("missing 'imageQualityPolicy' in site-schema");
const rawRoutingMode = jsonData.languageRoutingMode ?? "auto";
const languageRoutingMode = rawRoutingMode === "auto"
? languages.length === 1
? "prefixSecondary"
: "prefixAll"
: rawRoutingMode;
const newSchema = {
languages,
isMultilingual: languages.length > 1,
languageRoutingMode,
languageLabels: makeSiteLanguageLabels(languages, lib.common.languageLabels),
defaultLanguage: languages[0],
nodeTypes: {
_site: undefined,
home: undefined,
},
mediaPolicies: {
default: undefined,
},
imageQualityPolicy,
l10n,
};
const ctx = {
lib,
newSchema,
mediaPoliciesJsonData: new Map((jsonData.mediaPolicies ?? []).map((p) => [p.policyName, p])),
importTypeNames: [],
trusted,
};
appendNodeTypesToSiteSchema(ctx, jsonData);
importNodeTypesToSiteSchema(ctx);
injectAuthorsTaxonomyIfNeeded(ctx);
await completeSchemaLocales(regSite.siteDir, l10n);
fillLabelingFieldLabelsFromTaxonomies(newSchema);
const finalLanguages = Object.keys(l10n);
newSchema.languageLabels = makeSiteLanguageLabels(finalLanguages, lib.common.languageLabels);
if (!newSchema.mediaPolicies.default) {
const mediaPolicyJsonData = ctx.mediaPoliciesJsonData.get("default");
if (mediaPolicyJsonData) {
if (!mediaPolicyJsonData.image) {
throw new Error(`missing 'image' in media policy 'default'`);
}
newSchema.mediaPolicies.default = mediaPolicyJsonData;
}
else {
newSchema.mediaPolicies.default = defaultMediaPolicy;
}
}
checkIntegrityOfSiteSchema(newSchema);
return {
upToDate: true,
siteSchema: newSchema,
plugins: Array.from(lib.plugins?.values() ?? []),
siteConfiguration: jsonData.configuration ?? {},
};
}
function appendNodeTypesToSiteSchema(ctx, jsonData) {
const { newSchema } = ctx;
for (const item of jsonData.nodeTypes ?? []) {
const typeName = item.kind === "site" ? "_site" : item.typeName;
if (newSchema.nodeTypes[typeName])
throw new Error(`Duplicated node type '${typeName}'`);
switch (item.kind) {
case "site":
newSchema.nodeTypes._site = toSiteDataType(ctx, item);
break;
case "document":
newSchema.nodeTypes[item.typeName] = toDocumentType(ctx, {
jsonData: item,
languages: newSchema.languages,
});
break;
case "part":
newSchema.nodeTypes[item.typeName] = toPartType(ctx, item, newSchema.l10n);
break;
default:
throw new Error(`unknown node type kind '${item.kind}'`);
}
}
if (!newSchema.nodeTypes._site) {
newSchema.nodeTypes._site = toSiteDataType(ctx, undefined);
}
if (!newSchema.nodeTypes.home) {
importDocumentType(ctx, "home", { documentKind: "routing" });
}
const { _site, home, ...rest } = newSchema.nodeTypes;
newSchema.nodeTypes = { _site, home, ...rest };
}
function importNodeTypesToSiteSchema(ctx) {
const { newSchema, importTypeNames } = ctx;
while (true) {
const item = importTypeNames.shift();
if (!item)
break;
if (newSchema.nodeTypes[item.typeName])
continue;
switch (item.kind) {
case "document":
importDocumentType(ctx, item.typeName, {
skipDuplicate: true,
documentKind: item.documentKind,
});
break;
case "part":
importPartType(ctx, item.typeName, { skipDuplicate: true });
break;
default:
throw new Error(`unknown node type kind '${item.kind}'`);
}
}
}
function injectAuthorsTaxonomyIfNeeded(ctx) {
const { newSchema } = ctx;
const authorsType = newSchema.nodeTypes.authors;
if (authorsType) {
if (authorsType.kind !== "document" || authorsType.documentKind !== "routing") {
throw new Error("'authors' must be a routing document type");
}
if (!authorsType.regularChildren || authorsType.regularChildren.length === 0) {
throw new Error("'authors' routing document must have at least one child type");
}
}
else {
importDocumentType(ctx, "author", { documentKind: "regular", skipDuplicate: true });
importDocumentType(ctx, "authors", { documentKind: "routing", skipDuplicate: true });
}
const homeType = newSchema.nodeTypes.home;
if (!homeType)
return;
const currentRoutingChildren = homeType.routingChildren ?? [];
if (!currentRoutingChildren.includes("authors")) {
homeType.routingChildren = [...currentRoutingChildren, "authors"];
}
}
function toSiteDataType(ctx, jsonData) {
const { newSchema, lib } = ctx;
importPartOfL10n({
from: lib.common.l10n,
fromPath: ["siteFields"],
to: newSchema.l10n,
toPath: ["nodeTypes", "_site", "fields"],
ifDuplicate: "overwrite",
});
const jsonSiteFields = jsonData?.fields ?? lib.schema.nodeTypes?._site?.fields ?? [];
const siteFields = jsonSiteFields.map((item) => typeof item === "string"
? importFieldType(ctx, {
qualifiedFieldName: item,
fillL10n: newSchema.l10n,
fillL10nPath: ["nodeTypes", "_site", "fields"],
})
: toFieldType(ctx, item));
checkSiteFieldNames(siteFields, lib);
const siteType = {
kind: "site",
typeName: "_site",
fields: [...lib.common.siteFields.map((f) => toFieldType(ctx, f)), ...siteFields],
};
if (!ctx.trusted) {
const contactEmail = siteType.fields.find((f) => f.name === "contactEmail");
if (contactEmail) {
contactEmail.readOnly = true;
}
}
return siteType;
}
function toDocumentType(ctx, { jsonData, languages, }) {
const { newSchema } = ctx;
const jsonFields = jsonData.fields;
const fields = jsonFields === undefined
? undefined
: jsonFields.map((item) => typeof item === "string"
? importFieldType(ctx, {
qualifiedFieldName: item,
fillL10n: newSchema.l10n,
fillL10nPath: ["nodeTypes", jsonData.typeName, "fields"],
})
: toFieldType(ctx, item));
if (fields)
checkDuplicatedFieldNames(fields);
for (const list of jsonData.lists ?? []) {
for (const typeName of list.parts) {
ctx.importTypeNames.push({ typeName, kind: "part" });
}
}
if (jsonData.documentKind === "routing") {
return toRoutingDocumentType(jsonData, languages, fields);
}
return toRegularDocumentSchema(jsonData, fields);
}
function importDocumentTypeDependencies(ctx, jsonData) {
for (const listType of jsonData.lists ?? []) {
for (const typeName of listType.parts) {
ctx.importTypeNames.push({ typeName, kind: "part" });
}
}
for (const childTypeName of jsonData.routingChildren ?? []) {
ctx.importTypeNames.push({
typeName: childTypeName,
kind: "document",
documentKind: "routing",
});
}
for (const childTypeName of jsonData.regularChildren ?? []) {
ctx.importTypeNames.push({
typeName: childTypeName,
kind: "document",
documentKind: "regular",
});
}
if (jsonData.mediaPolicy) {
importMediaPolicy(ctx, jsonData.mediaPolicy, { skipDuplicate: true });
}
}
function toRoutingDocumentType(jsonData, languages, fields) {
const kebabName = camelToKebabCase(jsonData.typeName);
let route;
if (jsonData.typeName !== "home") {
if (!jsonData.route) {
throw new Error(`missing route in '${jsonData.typeName}'`);
}
route = toUrlPaths(jsonData.route, languages);
}
const children = jsonData.regularChildren && jsonData.regularChildren.length > 0
? jsonData.regularChildren
: undefined;
if (children && !jsonData.regularChildrenSorting) {
throw new Error(`Missing 'regularChildrenSorting' in "${jsonData.typeName}`);
}
if (jsonData.hasFrontendApp && children) {
throw new Error(`Document "${jsonData.typeName}" can't have child documents if it is a frontend application`);
}
if (jsonData.hasFrontendApp && jsonData.redirectTo) {
throw new Error(`Document "${jsonData.typeName}" can't be a redirection if it is a frontend application`);
}
const derivedOgType = jsonData.ogType ?? deriveOgTypeFromJsonLdType(jsonData.jsonLdType);
return {
kind: "document",
documentKind: "routing",
typeName: jsonData.typeName,
kebabName,
route,
redirectTo: jsonData.typeName === "home" ? undefined : jsonData.redirectTo,
fields,
mediaPolicy: jsonData.mediaPolicy,
lists: jsonData.lists ? jsonData.lists.map(toListType) : undefined,
routingChildren: jsonData.routingChildren,
regularChildren: children,
regularChildrenSorting: jsonData.regularChildrenSorting
? parseSorting(jsonData.regularChildrenSorting)
: undefined,
childLimit: jsonData.childLimit,
hasFrontendApp: jsonData.hasFrontendApp,
useUrlQuery: jsonData.useUrlQuery,
templateNames: [kebabName],
ogType: derivedOgType,
jsonLdType: jsonData.jsonLdType,
withFeaturedImage: jsonData.withFeaturedImage,
cluster: jsonData.cluster,
adminUi: jsonData.adminUi,
};
}
function toListType(item) {
return {
listName: item.listName,
sorting: parseSorting(item.sorting),
parts: item.parts,
limit: item.limit,
};
}
function toUrlPaths(route, languages, defaultUrlPaths) {
const urlPaths = {};
if (typeof route === "string") {
for (const language of languages) {
urlPaths[language] = route;
}
}
else {
const defaultLanguage = languages[0];
if (!defaultLanguage)
throw new Error("missing languages in site-schema");
for (const language of languages) {
const val = route[language] ?? defaultUrlPaths?.[language] ?? route[defaultLanguage];
if (!val)
throw new Error(`missing route for language "${language}" in site-schema`);
if (val.includes(":"))
throw new Error("route should not contain ':'");
urlPaths[language] = val;
}
}
return urlPaths;
}
function toRegularDocumentSchema(jsonData, fields) {
if (jsonData.route &&
(typeof jsonData.route !== "string" || !authorizedClassicRoute.has(jsonData.route))) {
throw new Error(`invalid route '${jsonData.route}'`);
}
const kebabName = camelToKebabCase(jsonData.typeName);
if (!jsonData.route) {
throw new Error(`missing route in '${jsonData.typeName}'`);
}
const children = jsonData.regularChildren && jsonData.regularChildren.length > 0
? jsonData.regularChildren
: undefined;
if (children && !jsonData.regularChildrenSorting) {
throw new Error(`Missing 'regularChildrenSorting' in "${jsonData.typeName}`);
}
if (jsonData.hasFrontendApp && children) {
throw new Error(`Document "${jsonData.typeName}" can't have child documents if it is a frontend application`);
}
const derivedOgType = jsonData.ogType ?? deriveOgTypeFromJsonLdType(jsonData.jsonLdType);
return {
kind: "document",
documentKind: "regular",
typeName: jsonData.typeName,
kebabName,
route: jsonData.route,
fields,
mediaPolicy: jsonData.mediaPolicy,
lists: jsonData.lists ? jsonData.lists.map(toListType) : undefined,
routingChildren: jsonData.routingChildren,
regularChildren: children,
regularChildrenSorting: jsonData.regularChildrenSorting
? parseSorting(jsonData.regularChildrenSorting)
: undefined,
childLimit: jsonData.childLimit,
hasFrontendApp: jsonData.hasFrontendApp,
useUrlQuery: jsonData.useUrlQuery,
autoPublish: jsonData.autoPublish,
relativeIdGenerator: formatRelativeIdGeneratorSchema(jsonData.relativeIdGenerator, {
varName: "relativeIdGenerator",
}),
templateNames: [kebabName],
ogType: derivedOgType,
jsonLdType: jsonData.jsonLdType,
withFeaturedImage: jsonData.withFeaturedImage,
cluster: jsonData.cluster,
adminUi: jsonData.adminUi,
};
}
function formatRelativeIdGeneratorSchema(val, formatOptions) {
if (!val)
return;
if (val.length === 0) {
throw new Error(`invalid empty value for '${formatOptions.varName}'`);
}
val[0] = StringAT.assert(val[0]);
return val;
}
function fillLabelingFieldLabelsFromTaxonomies(siteSchema) {
const { nodeTypes, l10n } = siteSchema;
for (const nodeType of Object.values(nodeTypes)) {
if (nodeType.kind === "site")
continue;
if (!nodeType.fields)
continue;
const typeName = nodeType.typeName;
for (const field of nodeType.fields) {
if (field.dataType !== "labeling")
continue;
const taxonomyTypeName = field.taxonomy;
for (const language of Object.keys(l10n)) {
const existingNodeTypeLocales = l10n[language]?.nodeTypes?.[typeName];
const existingFieldsLocales = existingNodeTypeLocales && typeof existingNodeTypeLocales !== "string"
? existingNodeTypeLocales.fields
: undefined;
const existingFieldLocales = existingFieldsLocales && typeof existingFieldsLocales !== "string"
? existingFieldsLocales[field.name]
: undefined;
const existingFieldLabel = existingFieldLocales && typeof existingFieldLocales !== "string"
? existingFieldLocales.label
: undefined;
if (existingFieldLabel)
continue;
const taxonomyNodeTypeLocales = l10n[language]?.nodeTypes?.[taxonomyTypeName];
const taxonomyLabel = taxonomyNodeTypeLocales && typeof taxonomyNodeTypeLocales !== "string"
? taxonomyNodeTypeLocales.label
: undefined;
if (!taxonomyLabel)
continue;
if (!l10n[language].nodeTypes[typeName]) {
l10n[language].nodeTypes[typeName] = {};
}
const targetNodeTypeLocales = l10n[language].nodeTypes[typeName];
if (typeof targetNodeTypeLocales === "string")
continue;
if (!targetNodeTypeLocales.fields) {
targetNodeTypeLocales.fields = {};
}
const targetFieldsLocales = targetNodeTypeLocales.fields;
if (typeof targetFieldsLocales === "string")
continue;
if (!targetFieldsLocales[field.name]) {
targetFieldsLocales[field.name] = {};
}
const targetFieldLocales = targetFieldsLocales[field.name];
if (typeof targetFieldLocales === "string")
continue;
targetFieldLocales.label = taxonomyLabel;
}
}
}
}
function checkIntegrityOfSiteSchema(newSchema) {
if (newSchema.nodeTypes.home &&
(newSchema.nodeTypes.home.kind !== "document" ||
newSchema.nodeTypes.home.documentKind !== "routing")) {
throw new Error("Invalid home document type");
}
for (const nodeType of Object.values(newSchema.nodeTypes)) {
if (nodeType.kind !== "document")
continue;
let childRoute;
for (const childTypeName of nodeType.regularChildren ?? []) {
const child = newSchema.nodeTypes[childTypeName];
if (!child || child.kind !== "document") {
throw new Error(`Unknown child document type '${childTypeName}'`);
}
if (child.documentKind === "regular") {
if (childRoute === undefined) {
childRoute = child.route;
}
else if (child.route !== childRoute) {
throw new Error(`Inconsistent routes '${childRoute}' and '${child.route}' for children of document type '${nodeType.typeName}'`);
}
}
}
}
for (const nodeType of Object.values(newSchema.nodeTypes)) {
if (nodeType.kind !== "document")
continue;
validateRoutingClusterConstraints(nodeType, newSchema);
}
for (const nodeType of Object.values(newSchema.nodeTypes)) {
if (nodeType.kind !== "document")
continue;
if (nodeType.typeName === "home")
continue;
if (nodeType.routingChildren?.includes("authors")) {
throw new Error(`Document type "${nodeType.typeName}" cannot have "authors" as a routing child. Only "home" can have "authors".`);
}
}
}
function validateRoutingClusterConstraints(nodeType, schema) {
if (nodeType.documentKind === "routing" && nodeType.typeName !== "home" && nodeType.cluster) {
throw new Error(`Routing document "${nodeType.typeName}" cannot have cluster property`);
}
if (nodeType.documentKind === "regular" && nodeType.route === ":yyyy/:mm/:dd/:relativeId-:slug") {
if ((nodeType.regularChildren?.length ?? 0) > 0 ||
(nodeType.routingChildren?.length ?? 0) > 0) {
throw new Error(`Regular document "${nodeType.typeName}" with date-based route cannot have children`);
}
}
if (nodeType.routingChildren) {
for (const childName of nodeType.routingChildren) {
const child = schema.nodeTypes[childName];
if (!child) {
throw new Error(`Unknown routing child "${childName}" in "${nodeType.typeName}"`);
}
if (child.kind !== "document" || child.documentKind !== "routing") {
throw new Error(`Child "${childName}" of "${nodeType.typeName}" must be a routing document`);
}
}
}
}
function toPartType(ctx, jsonData, l10n) {
const fields = jsonData.fields === undefined
? undefined
: jsonData.fields.map((item) => typeof item === "string"
? importFieldType(ctx, {
qualifiedFieldName: item,
fillL10n: l10n,
fillL10nPath: ["nodeTypes", jsonData.typeName, "fields"],
})
: toFieldType(ctx, item));
if (fields)
checkDuplicatedFieldNames(fields);
if (jsonData.list) {
for (const listType of jsonData.list.parts) {
ctx.importTypeNames.push({ typeName: listType, kind: "part" });
}
}
const kebabName = camelToKebabCase(jsonData.typeName);
return {
kind: "part",
typeName: jsonData.typeName,
kebabName,
fields,
mediaPolicy: jsonData.mediaPolicy,
list: jsonData.list
? {
listName: PART_DEFAULT_LIST_NAME,
parts: jsonData.list.parts,
limit: jsonData.list.limit,
sorting: parseSorting(jsonData.list.sorting),
}
: undefined,
};
}
function importDocumentType(ctx, typeName, options) {
const { newSchema, lib } = ctx;
const { documentKind, skipDuplicate } = options;
const existingType = newSchema.nodeTypes[typeName];
if (existingType) {
if (existingType.kind !== "document") {
throw new Error(`Node type '${typeName}' can't be both a document and a ${existingType.kind}`);
}
if (existingType.documentKind !== documentKind) {
throw new Error(`Node type '${typeName}' can't be both ${documentKind} and ${existingType.documentKind}`);
}
if (skipDuplicate)
return;
throw new Error(`Duplicated document type '${typeName}'`);
}
const jsonData = lib.schema.nodeTypes[typeName];
if (!jsonData || jsonData.kind !== "document") {
throw new Error(`Unknown document type '${typeName}'`);
}
importPartOfL10n({
from: lib.l10n,
fromPath: ["nodeTypes", typeName],
to: newSchema.l10n,
});
if (jsonData.documentKind !== documentKind) {
throw new Error(`Can't import document type "${typeName}" because it is not a "${documentKind}"`);
}
newSchema.nodeTypes[typeName] = toDocumentType(ctx, {
jsonData,
languages: newSchema.languages,
});
importDocumentTypeDependencies(ctx, jsonData);
}
function importPartType(ctx, typeName, options = {}) {
const { newSchema, lib } = ctx;
const existingType = newSchema.nodeTypes[typeName];
if (existingType) {
if (existingType.kind !== "part")
throw new Error(`Duplicated node type '${typeName}'`);
if (options.skipDuplicate)
return;
throw new Error(`Duplicated part type '${typeName}'`);
}
const jsonData = lib.schema.nodeTypes[typeName];
if (!jsonData || jsonData.kind !== "part")
throw new Error(`Unknown part type '${typeName}'`);
importPartOfL10n({
from: lib.l10n,
fromPath: ["nodeTypes", typeName],
to: newSchema.l10n,
});
newSchema.nodeTypes[typeName] = toPartType(ctx, jsonData, newSchema.l10n);
if (jsonData.mediaPolicy) {
importMediaPolicy(ctx, jsonData.mediaPolicy, { skipDuplicate: true });
}
}
function importMediaPolicy(ctx, policyName, options = {}) {
const { newSchema, lib } = ctx;
if (newSchema.mediaPolicies[policyName]) {
if (options.skipDuplicate)
return;
throw new Error(`Duplicate media policy '${policyName}'`);
}
const jsonData = ctx.mediaPoliciesJsonData.get(policyName) ?? lib.schema.mediaPolicies[policyName];
if (!jsonData)
throw new Error(`Unknown media policy '${policyName}'`);
newSchema.mediaPolicies[policyName] = jsonData;
}
async function readJtSiteSchema(file) {
const json = await readFile(file, {
encoding: "utf8",
});
const data = JSON.parse(json);
const ver = data.ParoiCMSSiteSchemaFormatVersion ??
(data.schemaEngineVersion === "1.0.0" ? "1.0.0" : data.version);
if (typeof ver === "string" && ver !== siteSchemaFormatVersion) {
return {
upToDate: false,
siteSchemaFormatVersion: ver,
jsonData: data,
};
}
checkSiteSchemaFormatVersion(ver, { file });
const result = jsonTypeValidator.validate("JtSiteSchema", data);
if (!result.valid) {
throw new Error(`invalid site-schema '${file}': ${result.error ?? "(missing message)"}`);
}
return {
upToDate: true,
jsonData: data,
};
}
function importFieldType(ctx, { qualifiedFieldName, fillL10n, fillL10nPath, }) {
const { lib } = ctx;
if (!(qualifiedFieldName in lib.schema.fieldTypes)) {
throw new Error(`unknown field '${qualifiedFieldName}'`);
}
const field = toFieldType(ctx, lib.schema.fieldTypes[qualifiedFieldName]);
importPartOfL10n({
from: lib.l10n,
fromPath: ["fieldTypes", qualifiedFieldName],
to: fillL10n,
toPath: [...fillL10nPath, field.name],
});
return field;
}
function toFieldType(ctx, jsonData) {
const { plugin, ...rawField } = jsonData;
if (jsonData.name.includes("[") || jsonData.name.includes("]")) {
throw new Error(`Invalid field name '${jsonData.name}': field names must not contain '[' or ']' characters. ` +
`Use plain name with separate 'plugin' property for plugin fields.`);
}
const qualifiedName = plugin ? `${jsonData.name}[${plugin}]` : jsonData.name;
const field = rawField.storedAs === "labeling"
? {
...rawField,
dataType: "labeling",
qualifiedName,
}
: rawField.storedAs === "partField"
? {
...rawField,
localized: false,
dataType: "partField",
qualifiedName,
}
: {
...rawField,
qualifiedName,
};
if (plugin) {
if (!ctx.lib.plugins?.has(plugin)) {
throw new Error(`[${jsonData.name}] Unknown plugin '${plugin}'`);
}
field.pluginName = plugin;
}
if (jsonData.storedAs === "labeling") {
ctx.importTypeNames.push({
typeName: jsonData.taxonomy,
kind: "document",
documentKind: "routing",
});
}
if (jsonData.storedAs === "partField") {
if (typeof jsonData.partType !== "string") {
throw new Error(`[${jsonData.name}] 'partType' must be a string`);
}
ctx.importTypeNames.push({ typeName: jsonData.partType, kind: "part" });
}
return field;
}
function checkDuplicatedFieldNames(fields) {
const names = fields.map((item) => item.name);
const set = new Set(names);
if (set.size !== fields.length) {
const details = fields
.map((f) => (f.qualifiedName !== f.name ? `${f.name}⇒${f.qualifiedName}` : f.name))
.join(", ");
throw new Error(`duplicated fields: ${details}`);
}
}
function checkSiteFieldNames(fields, lib) {
const names = fields.map((item) => item.name);
const set = new Set(names);
if (set.size !== fields.length)
throw new Error(`duplicated fields: ${names.join(", ")}`);
const invalidNames = lib.common.siteFields.map((item) => item.name);
invalidNames.push(...invalidFieldNames);
for (const invalidName of invalidNames) {
if (set.has(invalidName))
throw new Error(`invalid field name "${invalidName}"`);
}
}
function camelToKebabCase(s) {
return s
.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`)
.replace(/^-+/, "")
.replace(/-+$/, "")
.replace(/--+/g, "-");
}
//# sourceMappingURL=site-schema-factory.js.map