payload
Version:
Node, React, Headless CMS and Application Framework built on Next.js
1,142 lines (1,141 loc) • 46.9 kB
JavaScript
// @ts-strict-ignore
import pluralize from 'pluralize';
const { singular } = pluralize;
import { MissingEditorProp } from '../errors/MissingEditorProp.js';
import { fieldAffectsData } from '../fields/config/types.js';
import { generateJobsJSONSchemas } from '../queues/config/generateJobsJSONSchemas.js';
import { toWords } from './formatLabels.js';
import { getCollectionIDFieldTypes } from './getCollectionIDFieldTypes.js';
const fieldIsRequired = (field)=>{
const isConditional = Boolean(field?.admin && field?.admin?.condition);
if (isConditional) {
return false;
}
const isMarkedRequired = 'required' in field && field.required === true;
if (fieldAffectsData(field) && isMarkedRequired) {
return true;
}
// if any subfields are required, this field is required
if ('fields' in field && field.type !== 'array') {
return field.flattenedFields.some((subField)=>fieldIsRequired(subField));
}
return false;
};
function buildOptionEnums(options) {
return options.map((option)=>{
if (typeof option === 'object' && 'value' in option) {
return option.value;
}
return option;
});
}
function generateEntitySchemas(entities) {
const properties = [
...entities
].reduce((acc, { slug })=>{
acc[slug] = {
$ref: `#/definitions/${slug}`
};
return acc;
}, {});
return {
type: 'object',
additionalProperties: false,
properties,
required: Object.keys(properties)
};
}
function generateEntitySelectSchemas(entities) {
const properties = [
...entities
].reduce((acc, { slug })=>{
acc[slug] = {
$ref: `#/definitions/${slug}_select`
};
return acc;
}, {});
return {
type: 'object',
additionalProperties: false,
properties,
required: Object.keys(properties)
};
}
function generateCollectionJoinsSchemas(collections) {
const properties = [
...collections
].reduce((acc, { slug, joins, polymorphicJoins })=>{
const schema = {
type: 'object',
additionalProperties: false,
properties: {},
required: []
};
for(const collectionSlug in joins){
for (const join of joins[collectionSlug]){
schema.properties[join.joinPath] = {
type: 'string',
enum: [
collectionSlug
]
};
schema.required.push(join.joinPath);
}
}
for (const join of polymorphicJoins){
schema.properties[join.joinPath] = {
type: 'string',
enum: join.field.collection
};
schema.required.push(join.joinPath);
}
if (Object.keys(schema.properties).length > 0) {
acc[slug] = schema;
}
return acc;
}, {});
return {
type: 'object',
additionalProperties: false,
properties,
required: Object.keys(properties)
};
}
function generateLocaleEntitySchemas(localization) {
if (localization && 'locales' in localization && localization?.locales) {
const localesFromConfig = localization?.locales;
const locales = [
...localesFromConfig
].map((locale)=>{
return locale.code;
}, []);
return {
type: 'string',
enum: locales
};
}
return {
type: 'null'
};
}
function generateAuthEntitySchemas(entities) {
const properties = [
...entities
].filter(({ auth })=>Boolean(auth)).map(({ slug })=>{
return {
allOf: [
{
$ref: `#/definitions/${slug}`
},
{
type: 'object',
additionalProperties: false,
properties: {
collection: {
type: 'string',
enum: [
slug
]
}
},
required: [
'collection'
]
}
]
};
}, {});
return {
oneOf: properties
};
}
/**
* Generates the JSON Schema for database configuration
*
* @example { db: idType: string }
*/ function generateDbEntitySchema(config) {
const defaultIDType = config.db?.defaultIDType === 'number' ? {
type: 'number'
} : {
type: 'string'
};
return {
type: 'object',
additionalProperties: false,
properties: {
defaultIDType
},
required: [
'defaultIDType'
]
};
}
/**
* Returns a JSON Schema Type with 'null' added if the field is not required.
*/ export function withNullableJSONSchemaType(fieldType, isRequired) {
const fieldTypes = [
fieldType
];
if (isRequired) {
return fieldType;
}
fieldTypes.push('null');
return fieldTypes;
}
function entityOrFieldToJsDocs({ entity, i18n }) {
let description = undefined;
if (entity?.admin?.description) {
if (typeof entity?.admin?.description === 'string') {
description = entity?.admin?.description;
} else if (typeof entity?.admin?.description === 'object') {
if (entity?.admin?.description?.en) {
description = entity?.admin?.description?.en;
} else if (entity?.admin?.description?.[i18n.language]) {
description = entity?.admin?.description?.[i18n.language];
}
} else if (typeof entity?.admin?.description === 'function' && i18n) {
// do not evaluate description functions for generating JSDocs. The output of
// those can differ depending on where and when they are called, creating
// inconsistencies in the generated JSDocs.
}
}
return description;
}
export function fieldsToJSONSchema(/**
* Used for relationship fields, to determine whether to use a string or number type for the ID.
* While there is a default ID field type set by the db adapter, they can differ on a collection-level
* if they have custom ID fields.
*/ collectionIDFieldTypes, fields, /**
* Allows you to define new top-level interfaces that can be re-used in the output schema.
*/ interfaceNameDefinitions, config, i18n) {
const requiredFieldNames = new Set();
return {
properties: Object.fromEntries(fields.reduce((fieldSchemas, field, index)=>{
const isRequired = fieldAffectsData(field) && fieldIsRequired(field);
const fieldDescription = entityOrFieldToJsDocs({
entity: field,
i18n
});
const baseFieldSchema = {};
if (fieldDescription) {
baseFieldSchema.description = fieldDescription;
}
let fieldSchema;
switch(field.type){
case 'array':
{
fieldSchema = {
...baseFieldSchema,
type: withNullableJSONSchemaType('array', isRequired),
items: {
type: 'object',
additionalProperties: false,
...fieldsToJSONSchema(collectionIDFieldTypes, field.flattenedFields, interfaceNameDefinitions, config, i18n)
}
};
if (field.interfaceName) {
interfaceNameDefinitions.set(field.interfaceName, fieldSchema);
fieldSchema = {
$ref: `#/definitions/${field.interfaceName}`
};
}
break;
}
case 'blocks':
{
// Check for a case where no blocks are provided.
// We need to generate an empty array for this case, note that JSON schema 4 doesn't support empty arrays
// so the best we can get is `unknown[]`
const hasBlocks = Boolean(field.blockReferences ? field.blockReferences.length : field.blocks.length);
fieldSchema = {
...baseFieldSchema,
type: withNullableJSONSchemaType('array', isRequired),
items: hasBlocks ? {
oneOf: (field.blockReferences ?? field.blocks).map((block)=>{
if (typeof block === 'string') {
const resolvedBlock = config?.blocks?.find((b)=>b.slug === block);
return {
$ref: `#/definitions/${resolvedBlock.interfaceName ?? resolvedBlock.slug}`
};
}
const blockFieldSchemas = fieldsToJSONSchema(collectionIDFieldTypes, block.flattenedFields, interfaceNameDefinitions, config, i18n);
const blockSchema = {
type: 'object',
additionalProperties: false,
properties: {
...blockFieldSchemas.properties,
blockType: {
const: block.slug
}
},
required: [
'blockType',
...blockFieldSchemas.required
]
};
if (block.interfaceName) {
interfaceNameDefinitions.set(block.interfaceName, blockSchema);
return {
$ref: `#/definitions/${block.interfaceName}`
};
}
return blockSchema;
})
} : {}
};
break;
}
case 'checkbox':
{
fieldSchema = {
...baseFieldSchema,
type: withNullableJSONSchemaType('boolean', isRequired)
};
break;
}
case 'code':
case 'date':
case 'email':
case 'textarea':
{
fieldSchema = {
...baseFieldSchema,
type: withNullableJSONSchemaType('string', isRequired)
};
break;
}
case 'group':
case 'tab':
{
fieldSchema = {
...baseFieldSchema,
type: 'object',
additionalProperties: false,
...fieldsToJSONSchema(collectionIDFieldTypes, field.flattenedFields, interfaceNameDefinitions, config, i18n)
};
if (field.interfaceName) {
interfaceNameDefinitions.set(field.interfaceName, fieldSchema);
fieldSchema = {
$ref: `#/definitions/${field.interfaceName}`
};
}
break;
}
case 'join':
{
let items;
if (Array.isArray(field.collection)) {
items = {
oneOf: field.collection.map((collection)=>({
type: 'object',
additionalProperties: false,
properties: {
relationTo: {
const: collection
},
value: {
oneOf: [
{
type: collectionIDFieldTypes[collection]
},
{
$ref: `#/definitions/${collection}`
}
]
}
},
required: [
'collectionSlug',
'value'
]
}))
};
} else {
items = {
oneOf: [
{
type: collectionIDFieldTypes[field.collection]
},
{
$ref: `#/definitions/${field.collection}`
}
]
};
}
fieldSchema = {
...baseFieldSchema,
type: 'object',
additionalProperties: false,
properties: {
docs: {
type: 'array',
items
},
hasNextPage: {
type: 'boolean'
},
totalDocs: {
type: 'number'
}
}
};
break;
}
case 'json':
{
fieldSchema = field.jsonSchema?.schema || {
...baseFieldSchema,
type: [
'object',
'array',
'string',
'number',
'boolean',
'null'
]
};
break;
}
case 'number':
{
if (field.hasMany === true) {
fieldSchema = {
...baseFieldSchema,
type: withNullableJSONSchemaType('array', isRequired),
items: {
type: 'number'
}
};
} else {
fieldSchema = {
...baseFieldSchema,
type: withNullableJSONSchemaType('number', isRequired)
};
}
break;
}
case 'point':
{
fieldSchema = {
...baseFieldSchema,
type: withNullableJSONSchemaType('array', isRequired),
items: [
{
type: 'number'
},
{
type: 'number'
}
],
maxItems: 2,
minItems: 2
};
break;
}
case 'radio':
{
fieldSchema = {
...baseFieldSchema,
type: withNullableJSONSchemaType('string', isRequired),
enum: buildOptionEnums(field.options)
};
if (field.interfaceName) {
interfaceNameDefinitions.set(field.interfaceName, fieldSchema);
fieldSchema = {
$ref: `#/definitions/${field.interfaceName}`
};
}
break;
}
case 'relationship':
case 'upload':
{
if (Array.isArray(field.relationTo)) {
if (field.hasMany) {
fieldSchema = {
...baseFieldSchema,
type: withNullableJSONSchemaType('array', isRequired),
items: {
oneOf: field.relationTo.map((relation)=>{
return {
type: 'object',
additionalProperties: false,
properties: {
relationTo: {
const: relation
},
value: {
oneOf: [
{
type: collectionIDFieldTypes[relation]
},
{
$ref: `#/definitions/${relation}`
}
]
}
},
required: [
'value',
'relationTo'
]
};
})
}
};
} else {
fieldSchema = {
...baseFieldSchema,
oneOf: field.relationTo.map((relation)=>{
return {
type: withNullableJSONSchemaType('object', isRequired),
additionalProperties: false,
properties: {
relationTo: {
const: relation
},
value: {
oneOf: [
{
type: collectionIDFieldTypes[relation]
},
{
$ref: `#/definitions/${relation}`
}
]
}
},
required: [
'value',
'relationTo'
]
};
})
};
}
} else if (field.hasMany) {
fieldSchema = {
...baseFieldSchema,
type: withNullableJSONSchemaType('array', isRequired),
items: {
oneOf: [
{
type: collectionIDFieldTypes[field.relationTo]
},
{
$ref: `#/definitions/${field.relationTo}`
}
]
}
};
} else {
fieldSchema = {
...baseFieldSchema,
oneOf: [
{
type: withNullableJSONSchemaType(collectionIDFieldTypes[field.relationTo], isRequired)
},
{
$ref: `#/definitions/${field.relationTo}`
}
]
};
}
break;
}
case 'richText':
{
if (!field?.editor) {
throw new MissingEditorProp(field) // while we allow disabling editor functionality, you should not have any richText fields defined if you do not have an editor
;
}
if (typeof field.editor === 'function') {
throw new Error('Attempted to access unsanitized rich text editor.');
}
if (field.editor.outputSchema) {
fieldSchema = {
...baseFieldSchema,
...field.editor.outputSchema({
collectionIDFieldTypes,
config,
field,
i18n,
interfaceNameDefinitions,
isRequired
})
};
} else {
// Maintain backwards compatibility with existing rich text editors
fieldSchema = {
...baseFieldSchema,
type: withNullableJSONSchemaType('array', isRequired),
items: {
type: 'object'
}
};
}
break;
}
case 'select':
{
const optionEnums = buildOptionEnums(field.options);
// We get the previous field to check for a date in the case of a timezone select
// This works because timezone selects are always inserted right after a date with 'timezone: true'
const previousField = fields?.[index - 1];
const isTimezoneField = previousField?.type === 'date' && previousField.timezone && field.name.includes('_tz');
// Timezone selects should reference the supportedTimezones definition
if (isTimezoneField) {
fieldSchema = {
$ref: `#/definitions/supportedTimezones`
};
} else {
if (field.hasMany) {
fieldSchema = {
...baseFieldSchema,
type: withNullableJSONSchemaType('array', isRequired),
items: {
type: 'string'
}
};
if (optionEnums?.length) {
;
fieldSchema.items.enum = optionEnums;
}
} else {
fieldSchema = {
...baseFieldSchema,
type: withNullableJSONSchemaType('string', isRequired)
};
if (optionEnums?.length) {
fieldSchema.enum = optionEnums;
}
}
if (field.interfaceName) {
interfaceNameDefinitions.set(field.interfaceName, fieldSchema);
fieldSchema = {
$ref: `#/definitions/${field.interfaceName}`
};
}
break;
}
break;
}
case 'text':
if (field.hasMany === true) {
fieldSchema = {
...baseFieldSchema,
type: withNullableJSONSchemaType('array', isRequired),
items: {
type: 'string'
}
};
} else {
fieldSchema = {
...baseFieldSchema,
type: withNullableJSONSchemaType('string', isRequired)
};
}
break;
default:
{
break;
}
}
if ('typescriptSchema' in field && field?.typescriptSchema?.length) {
for (const schema of field.typescriptSchema){
fieldSchema = schema({
jsonSchema: fieldSchema
});
}
}
if (fieldSchema && fieldAffectsData(field)) {
if (isRequired && fieldSchema.required !== false) {
requiredFieldNames.add(field.name);
}
fieldSchemas.set(field.name, fieldSchema);
}
return fieldSchemas;
}, new Map())),
required: Array.from(requiredFieldNames)
};
}
// This function is part of the public API and is exported through payload/utilities
export function entityToJSONSchema(config, entity, interfaceNameDefinitions, defaultIDType, collectionIDFieldTypes, i18n) {
if (!collectionIDFieldTypes) {
collectionIDFieldTypes = getCollectionIDFieldTypes({
config,
defaultIDType
});
}
const title = entity.typescript?.interface ? entity.typescript.interface : singular(toWords(entity.slug, true));
let mutableFields = [
...entity.flattenedFields
];
const idField = {
name: 'id',
type: defaultIDType,
required: true
};
const customIdField = mutableFields.find((field)=>field.name === 'id');
if (customIdField && customIdField.type !== 'group' && customIdField.type !== 'tab') {
mutableFields = mutableFields.map((field)=>{
if (field === customIdField) {
return {
...field,
required: true
};
}
return field;
});
} else {
mutableFields.unshift(idField);
}
// mark timestamp fields required
if ('timestamps' in entity && entity.timestamps !== false) {
mutableFields = mutableFields.map((field)=>{
if (field.name === 'createdAt' || field.name === 'updatedAt') {
return {
...field,
required: true
};
}
return field;
});
}
if ('auth' in entity && entity.auth && (!entity.auth?.disableLocalStrategy || typeof entity.auth?.disableLocalStrategy === 'object' && entity.auth.disableLocalStrategy.enableFields)) {
mutableFields.push({
name: 'password',
type: 'text'
});
}
const jsonSchema = {
type: 'object',
additionalProperties: false,
title,
...fieldsToJSONSchema(collectionIDFieldTypes, mutableFields, interfaceNameDefinitions, config, i18n)
};
const entityDescription = entityOrFieldToJsDocs({
entity,
i18n
});
if (entityDescription) {
jsonSchema.description = entityDescription;
}
return jsonSchema;
}
export function fieldsToSelectJSONSchema({ config, fields, interfaceNameDefinitions }) {
const schema = {
type: 'object',
additionalProperties: false,
properties: {}
};
for (const field of fields){
switch(field.type){
case 'array':
case 'group':
case 'tab':
{
let fieldSchema = fieldsToSelectJSONSchema({
config,
fields: field.flattenedFields,
interfaceNameDefinitions
});
if (field.interfaceName) {
const definition = `${field.interfaceName}_select`;
interfaceNameDefinitions.set(definition, fieldSchema);
fieldSchema = {
$ref: `#/definitions/${definition}`
};
}
schema.properties[field.name] = {
oneOf: [
{
type: 'boolean'
},
fieldSchema
]
};
break;
}
case 'blocks':
{
const blocksSchema = {
type: 'object',
additionalProperties: false,
properties: {}
};
for (const block of field.blockReferences ?? field.blocks){
if (typeof block === 'string') {
continue; // TODO
}
let blockSchema = fieldsToSelectJSONSchema({
config,
fields: block.flattenedFields,
interfaceNameDefinitions
});
if (block.interfaceName) {
const definition = `${block.interfaceName}_select`;
interfaceNameDefinitions.set(definition, blockSchema);
blockSchema = {
$ref: `#/definitions/${definition}`
};
}
blocksSchema.properties[block.slug] = {
oneOf: [
{
type: 'boolean'
},
blockSchema
]
};
}
schema.properties[field.name] = {
oneOf: [
{
type: 'boolean'
},
blocksSchema
]
};
break;
}
default:
schema.properties[field.name] = {
type: 'boolean'
};
break;
}
}
return schema;
}
const fieldType = {
type: 'string',
required: false
};
const generateAuthFieldTypes = ({ type, loginWithUsername })=>{
if (loginWithUsername) {
switch(type){
case 'forgotOrUnlock':
{
if (loginWithUsername.allowEmailLogin) {
// allow email or username for unlock/forgot-password
return {
additionalProperties: false,
oneOf: [
{
additionalProperties: false,
properties: {
email: fieldType
},
required: [
'email'
]
},
{
additionalProperties: false,
properties: {
username: fieldType
},
required: [
'username'
]
}
]
};
} else {
// allow only username for unlock/forgot-password
return {
additionalProperties: false,
properties: {
username: fieldType
},
required: [
'username'
]
};
}
}
case 'login':
{
if (loginWithUsername.allowEmailLogin) {
// allow username or email and require password for login
return {
additionalProperties: false,
oneOf: [
{
additionalProperties: false,
properties: {
email: fieldType,
password: fieldType
},
required: [
'email',
'password'
]
},
{
additionalProperties: false,
properties: {
password: fieldType,
username: fieldType
},
required: [
'username',
'password'
]
}
]
};
} else {
// allow only username and password for login
return {
additionalProperties: false,
properties: {
password: fieldType,
username: fieldType
},
required: [
'username',
'password'
]
};
}
}
case 'register':
{
const requiredFields = [
'password'
];
const properties = {
password: fieldType,
username: fieldType
};
if (loginWithUsername.requireEmail) {
requiredFields.push('email');
}
if (loginWithUsername.requireUsername) {
requiredFields.push('username');
}
if (loginWithUsername.requireEmail || loginWithUsername.allowEmailLogin) {
properties.email = fieldType;
}
return {
additionalProperties: false,
properties,
required: requiredFields
};
}
}
}
// default email (and password for login/register)
return {
additionalProperties: false,
properties: {
email: fieldType,
password: fieldType
},
required: [
'email',
'password'
]
};
};
export function authCollectionToOperationsJSONSchema(config) {
const loginWithUsername = config.auth?.loginWithUsername;
const loginUserFields = generateAuthFieldTypes({
type: 'login',
loginWithUsername
});
const forgotOrUnlockUserFields = generateAuthFieldTypes({
type: 'forgotOrUnlock',
loginWithUsername
});
const registerUserFields = generateAuthFieldTypes({
type: 'register',
loginWithUsername
});
const properties = {
forgotPassword: forgotOrUnlockUserFields,
login: loginUserFields,
registerFirstUser: registerUserFields,
unlock: forgotOrUnlockUserFields
};
return {
type: 'object',
additionalProperties: false,
properties,
required: Object.keys(properties),
title: `${singular(toWords(`${config.slug}`, true))}AuthOperations`
};
}
// Generates the JSON Schema for supported timezones
export function timezonesToJSONSchema(supportedTimezones) {
return {
description: 'Supported timezones in IANA format.',
enum: supportedTimezones.map((timezone)=>typeof timezone === 'string' ? timezone : timezone.value)
};
}
function generateAuthOperationSchemas(collections) {
const properties = collections.reduce((acc, collection)=>{
if (collection.auth) {
acc[collection.slug] = {
$ref: `#/definitions/auth/${collection.slug}`
};
}
return acc;
}, {});
return {
type: 'object',
additionalProperties: false,
properties,
required: Object.keys(properties)
};
}
/**
* This is used for generating the TypeScript types (payload-types.ts) with the payload generate:types command.
*/ export function configToJSONSchema(config, defaultIDType, i18n) {
// a mutable Map to store custom top-level `interfaceName` types. Fields with an `interfaceName` property will be moved to the top-level definitions here
const interfaceNameDefinitions = new Map();
// Used for relationship fields, to determine whether to use a string or number type for the ID.
const collectionIDFieldTypes = getCollectionIDFieldTypes({
config,
defaultIDType
});
// Collections and Globals have to be moved to the top-level definitions as well. Reason: The top-level type will be the `Config` type - we don't want all collection and global
// types to be inlined inside the `Config` type
const entities = [
...config.globals.map((global)=>({
type: 'global',
entity: global
})),
...config.collections.map((collection)=>({
type: 'collection',
entity: collection
}))
];
const entityDefinitions = entities.reduce((acc, { type, entity })=>{
acc[entity.slug] = entityToJSONSchema(config, entity, interfaceNameDefinitions, defaultIDType, collectionIDFieldTypes, i18n);
const select = fieldsToSelectJSONSchema({
config,
fields: entity.flattenedFields,
interfaceNameDefinitions
});
if (type === 'global') {
select.properties.globalType = {
type: 'boolean'
};
}
acc[`${entity.slug}_select`] = {
type: 'object',
additionalProperties: false,
...select
};
return acc;
}, {});
const timezoneDefinitions = timezonesToJSONSchema(config.admin.timezones.supportedTimezones);
const authOperationDefinitions = [
...config.collections
].filter(({ auth })=>Boolean(auth)).reduce((acc, authCollection)=>{
acc.auth[authCollection.slug] = authCollectionToOperationsJSONSchema(authCollection);
return acc;
}, {
auth: {}
});
const jobsSchemas = config.jobs ? generateJobsJSONSchemas(config, config.jobs, interfaceNameDefinitions, collectionIDFieldTypes, i18n) : {};
const blocksDefinition = {
type: 'object',
additionalProperties: false,
properties: {},
required: []
};
if (config?.blocks?.length) {
for (const block of config.blocks){
const blockFieldSchemas = fieldsToJSONSchema(collectionIDFieldTypes, block.flattenedFields, interfaceNameDefinitions, config, i18n);
const blockSchema = {
type: 'object',
additionalProperties: false,
properties: {
...blockFieldSchemas.properties,
blockType: {
const: block.slug
}
},
required: [
'blockType',
...blockFieldSchemas.required
]
};
const interfaceName = block.interfaceName ?? block.slug;
interfaceNameDefinitions.set(interfaceName, blockSchema);
blocksDefinition.properties[block.slug] = {
$ref: `#/definitions/${interfaceName}`
};
blocksDefinition.required.push(block.slug);
}
}
let jsonSchema = {
additionalProperties: false,
definitions: {
supportedTimezones: timezoneDefinitions,
...entityDefinitions,
...Object.fromEntries(interfaceNameDefinitions),
...authOperationDefinitions
},
// These properties here will be very simple, as all the complexity is in the definitions. These are just the properties for the top-level `Config` type
type: 'object',
properties: {
auth: generateAuthOperationSchemas(config.collections),
blocks: blocksDefinition,
collections: generateEntitySchemas(config.collections || []),
collectionsJoins: generateCollectionJoinsSchemas(config.collections || []),
collectionsSelect: generateEntitySelectSchemas(config.collections || []),
db: generateDbEntitySchema(config),
globals: generateEntitySchemas(config.globals || []),
globalsSelect: generateEntitySelectSchemas(config.globals || []),
locale: generateLocaleEntitySchemas(config.localization),
user: generateAuthEntitySchemas(config.collections)
},
required: [
'user',
'locale',
'collections',
'collectionsSelect',
'collectionsJoins',
'globalsSelect',
'globals',
'auth',
'db',
'jobs',
'blocks'
],
title: 'Config'
};
if (jobsSchemas.definitions?.size) {
for (const [key, value] of jobsSchemas.definitions){
jsonSchema.definitions[key] = value;
}
}
if (jobsSchemas.properties) {
jsonSchema.properties.jobs = {
type: 'object',
additionalProperties: false,
properties: jobsSchemas.properties,
required: [
'tasks',
'workflows'
]
};
}
if (config?.typescript?.schema?.length) {
for (const schema of config.typescript.schema){
jsonSchema = schema({
collectionIDFieldTypes,
config,
i18n,
jsonSchema
});
}
}
return jsonSchema;
}
//# sourceMappingURL=configToJSONSchema.js.map