payload
Version:
Node, React, Headless CMS and Application Framework built on Next.js
262 lines (261 loc) • 13 kB
JavaScript
// @ts-strict-ignore
import { en } from '@payloadcms/translations/languages/en';
import { deepMergeSimple } from '@payloadcms/translations/utilities';
import { defaultUserCollection } from '../auth/defaultUser.js';
import { authRootEndpoints } from '../auth/endpoints/index.js';
import { sanitizeCollection } from '../collections/config/sanitize.js';
import { migrationsCollection } from '../database/migrations/migrationsCollection.js';
import { DuplicateCollection, InvalidConfiguration } from '../errors/index.js';
import { defaultTimezones } from '../fields/baseFields/timezone/defaultTimezones.js';
import { sanitizeGlobal } from '../globals/config/sanitize.js';
import { baseBlockFields, formatLabels, sanitizeFields } from '../index.js';
import { getLockedDocumentsCollection, lockedDocumentsCollectionSlug } from '../locked-documents/config.js';
import { getPreferencesCollection, preferencesCollectionSlug } from '../preferences/config.js';
import { getQueryPresetsConfig, queryPresetsCollectionSlug } from '../query-presets/config.js';
import { getDefaultJobsCollection, jobsCollectionSlug } from '../queues/config/index.js';
import { flattenBlock } from '../utilities/flattenAllFields.js';
import { getSchedulePublishTask } from '../versions/schedule/job.js';
import { addDefaultsToConfig } from './defaults.js';
import { setupOrderable } from './orderable/index.js';
const sanitizeAdminConfig = (configToSanitize)=>{
const sanitizedConfig = {
...configToSanitize
};
if (configToSanitize?.compatibility?.allowLocalizedWithinLocalized) {
process.env.NEXT_PUBLIC_PAYLOAD_COMPATIBILITY_allowLocalizedWithinLocalized = 'true';
}
// default logging level will be 'error' if not provided
sanitizedConfig.loggingLevels = {
Forbidden: 'info',
Locked: 'info',
MissingFile: 'info',
NotFound: 'info',
ValidationError: 'info',
...sanitizedConfig.loggingLevels || {}
};
// add default user collection if none provided
if (!sanitizedConfig?.admin?.user) {
const firstCollectionWithAuth = sanitizedConfig.collections.find(({ auth })=>Boolean(auth));
if (firstCollectionWithAuth) {
sanitizedConfig.admin.user = firstCollectionWithAuth.slug;
} else {
sanitizedConfig.admin.user = defaultUserCollection.slug;
sanitizedConfig.collections.push(defaultUserCollection);
}
}
const userCollection = sanitizedConfig.collections.find(({ slug })=>slug === sanitizedConfig.admin.user);
if (!userCollection || !userCollection.auth) {
throw new InvalidConfiguration(`${sanitizedConfig.admin.user} is not a valid admin user collection`);
}
if (sanitizedConfig?.admin?.timezones) {
if (typeof sanitizedConfig?.admin?.timezones?.supportedTimezones === 'function') {
sanitizedConfig.admin.timezones.supportedTimezones = sanitizedConfig.admin.timezones.supportedTimezones({
defaultTimezones
});
}
if (!sanitizedConfig?.admin?.timezones?.supportedTimezones) {
sanitizedConfig.admin.timezones.supportedTimezones = defaultTimezones;
}
} else {
sanitizedConfig.admin.timezones = {
supportedTimezones: defaultTimezones
};
}
// Timezones supported by the Intl API
const _internalSupportedTimezones = Intl.supportedValuesOf('timeZone');
sanitizedConfig.admin.timezones.supportedTimezones.forEach((timezone)=>{
if (!_internalSupportedTimezones.includes(timezone.value)) {
throw new InvalidConfiguration(`Timezone ${timezone.value} is not supported by the current runtime via the Intl API.`);
}
});
return sanitizedConfig;
};
export const sanitizeConfig = async (incomingConfig)=>{
const configWithDefaults = addDefaultsToConfig(incomingConfig);
const config = sanitizeAdminConfig(configWithDefaults);
// Add orderable fields
setupOrderable(config);
if (!config.endpoints) {
config.endpoints = [];
}
for (const endpoint of authRootEndpoints){
config.endpoints.push(endpoint);
}
if (config.localization && config.localization.locales?.length > 0) {
// clone localization config so to not break everything
const firstLocale = config.localization.locales[0];
if (typeof firstLocale === 'string') {
config.localization.localeCodes = [
...config.localization.locales
];
// is string[], so convert to Locale[]
config.localization.locales = config.localization.locales.map((locale)=>({
code: locale,
label: locale,
rtl: false,
toString: ()=>locale
}));
} else {
// is Locale[], so convert to string[] for localeCodes
config.localization.localeCodes = config.localization.locales.map((locale)=>locale.code);
config.localization.locales = config.localization.locales.map((locale)=>({
...locale,
toString: ()=>locale.code
}));
}
// Default fallback to true if not provided
config.localization.fallback = config.localization?.fallback ?? true;
}
const i18nConfig = {
fallbackLanguage: 'en',
supportedLanguages: {
en
},
translations: {}
};
if (incomingConfig?.i18n) {
i18nConfig.supportedLanguages = incomingConfig.i18n?.supportedLanguages || i18nConfig.supportedLanguages;
const supportedLangKeys = Object.keys(i18nConfig.supportedLanguages);
const fallbackLang = incomingConfig.i18n?.fallbackLanguage || i18nConfig.fallbackLanguage;
i18nConfig.fallbackLanguage = supportedLangKeys.includes(fallbackLang) ? fallbackLang : supportedLangKeys[0];
i18nConfig.translations = incomingConfig.i18n?.translations || i18nConfig.translations;
}
config.i18n = i18nConfig;
const richTextSanitizationPromises = [];
const schedulePublishCollections = [];
const queryPresetsCollections = [];
const schedulePublishGlobals = [];
const collectionSlugs = new Set();
const validRelationships = [
...config.collections.map((c)=>c.slug) ?? [],
jobsCollectionSlug,
lockedDocumentsCollectionSlug,
preferencesCollectionSlug
];
/**
* Blocks sanitization needs to happen before collections, as collection/global join field sanitization needs config.blocks
* to be populated with the sanitized blocks
*/ config.blocks = [];
if (incomingConfig.blocks?.length) {
for (const block of incomingConfig.blocks){
const sanitizedBlock = block;
if (sanitizedBlock._sanitized === true) {
continue;
}
sanitizedBlock._sanitized = true;
sanitizedBlock.fields = sanitizedBlock.fields.concat(baseBlockFields);
sanitizedBlock.labels = !sanitizedBlock.labels ? formatLabels(sanitizedBlock.slug) : sanitizedBlock.labels;
sanitizedBlock.fields = await sanitizeFields({
config: config,
existingFieldNames: new Set(),
fields: sanitizedBlock.fields,
parentIsLocalized: false,
richTextSanitizationPromises,
validRelationships
});
const flattenedSanitizedBlock = flattenBlock({
block
});
config.blocks.push(flattenedSanitizedBlock);
}
}
for(let i = 0; i < config.collections.length; i++){
if (collectionSlugs.has(config.collections[i].slug)) {
throw new DuplicateCollection('slug', config.collections[i].slug);
}
collectionSlugs.add(config.collections[i].slug);
const draftsConfig = config.collections[i]?.versions?.drafts;
if (typeof draftsConfig === 'object' && draftsConfig.schedulePublish) {
schedulePublishCollections.push(config.collections[i].slug);
}
if (config.collections[i].enableQueryPresets) {
queryPresetsCollections.push(config.collections[i].slug);
if (!validRelationships.includes(queryPresetsCollectionSlug)) {
validRelationships.push(queryPresetsCollectionSlug);
}
}
config.collections[i] = await sanitizeCollection(config, config.collections[i], richTextSanitizationPromises, validRelationships);
}
if (config.globals.length > 0) {
for(let i = 0; i < config.globals.length; i++){
const draftsConfig = config.globals[i]?.versions?.drafts;
if (typeof draftsConfig === 'object' && draftsConfig.schedulePublish) {
schedulePublishGlobals.push(config.globals[i].slug);
}
config.globals[i] = await sanitizeGlobal(config, config.globals[i], richTextSanitizationPromises, validRelationships);
}
}
if (schedulePublishCollections.length > 0 || schedulePublishGlobals.length > 0) {
if (!Array.isArray(configWithDefaults.jobs?.tasks)) {
configWithDefaults.jobs.tasks = [];
}
configWithDefaults.jobs.tasks.push(getSchedulePublishTask({
adminUserSlug: config.admin.user,
collections: schedulePublishCollections,
globals: schedulePublishGlobals
}));
}
// Need to add default jobs collection before locked documents collections
if (Array.isArray(configWithDefaults.jobs?.tasks) && configWithDefaults.jobs?.tasks?.length || Array.isArray(configWithDefaults.jobs?.workflows) && configWithDefaults.jobs?.workflows?.length) {
let defaultJobsCollection = getDefaultJobsCollection(config);
if (defaultJobsCollection) {
if (typeof configWithDefaults.jobs.jobsCollectionOverrides === 'function') {
defaultJobsCollection = configWithDefaults.jobs.jobsCollectionOverrides({
defaultJobsCollection
});
const hooks = defaultJobsCollection?.hooks;
// @todo - delete this check in 4.0
if (hooks && config?.jobs?.runHooks !== true) {
for (const hook of Object.keys(hooks)){
const defaultAmount = hook === 'afterRead' || hook === 'beforeChange' ? 1 : 0;
if (hooks[hook]?.length > defaultAmount) {
console.warn(`The jobsCollectionOverrides function is returning a collection with an additional ${hook} hook defined. These hooks will not run unless the jobs.runHooks option is set to true. Setting this option to true will negatively impact performance.`);
break;
}
}
}
}
const sanitizedJobsCollection = await sanitizeCollection(config, defaultJobsCollection, richTextSanitizationPromises, validRelationships);
configWithDefaults.collections.push(sanitizedJobsCollection);
}
}
configWithDefaults.collections.push(await sanitizeCollection(config, getLockedDocumentsCollection(config), richTextSanitizationPromises, validRelationships));
configWithDefaults.collections.push(await sanitizeCollection(config, getPreferencesCollection(config), richTextSanitizationPromises, validRelationships));
configWithDefaults.collections.push(await sanitizeCollection(config, migrationsCollection, richTextSanitizationPromises, validRelationships));
if (queryPresetsCollections.length > 0) {
configWithDefaults.collections.push(await sanitizeCollection(config, getQueryPresetsConfig(config), richTextSanitizationPromises, validRelationships));
}
if (config.serverURL !== '') {
config.csrf.push(config.serverURL);
}
// Get deduped list of upload adapters
if (!config.upload) {
config.upload = {
adapters: []
};
}
config.upload.adapters = Array.from(new Set(config.collections.map((c)=>c.upload?.adapter).filter(Boolean)));
// Pass through the email config as is so adapters don't break
if (incomingConfig.email) {
config.email = incomingConfig.email;
}
/*
Execute richText sanitization
*/ if (typeof incomingConfig.editor === 'function') {
config.editor = await incomingConfig.editor({
config: config,
isRoot: true,
parentIsLocalized: false
});
if (config.editor.i18n && Object.keys(config.editor.i18n).length >= 0) {
config.i18n.translations = deepMergeSimple(config.i18n.translations, config.editor.i18n);
}
}
const promises = [];
for (const sanitizeFunction of richTextSanitizationPromises){
promises.push(sanitizeFunction(config));
}
await Promise.all(promises);
return config;
};
//# sourceMappingURL=sanitize.js.map