UNPKG

@docusaurus/core

Version:

Easy to Maintain Open Source Documentation Websites

508 lines (507 loc) • 24.8 kB
"use strict"; /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.ConfigSchema = exports.DEFAULT_CONFIG = exports.DEFAULT_MARKDOWN_CONFIG = exports.DEFAULT_MARKDOWN_MDX1COMPAT = exports.DEFAULT_MARKDOWN_HOOKS = exports.DEFAULT_FUTURE_CONFIG = exports.DEFAULT_FUTURE_V4_CONFIG_TRUE = exports.DEFAULT_FUTURE_V4_CONFIG = exports.DEFAULT_FASTER_CONFIG_TRUE = exports.DEFAULT_FASTER_CONFIG = exports.DEFAULT_STORAGE_CONFIG = exports.DEFAULT_I18N_CONFIG = void 0; exports.validateConfig = validateConfig; const tslib_1 = require("tslib"); const utils_1 = require("@docusaurus/utils"); const utils_validation_1 = require("@docusaurus/utils-validation"); const utils_common_1 = require("@docusaurus/utils-common"); const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger")); const DEFAULT_I18N_LOCALE = 'en'; const SiteUrlSchema = utils_validation_1.Joi.string() .custom((value, helpers) => { try { const { pathname } = new URL(value); if (pathname !== '/') { return helpers.error('docusaurus.subPathError', { pathname }); } } catch { return helpers.error('any.invalid'); } return (0, utils_common_1.removeTrailingSlash)(value); }) .messages({ 'any.invalid': '"{#value}" does not look like a valid URL. Make sure it has a protocol; for example, "https://example.com".', 'docusaurus.subPathError': 'The url is not supposed to contain a sub-path like "{#pathname}". Please use the baseUrl field for sub-paths.', }); const BaseUrlSchema = utils_validation_1.Joi // Weird Joi trick needed, otherwise value '' is not normalized... .alternatives() .try(utils_validation_1.Joi.string().required().allow('')) .custom((value) => (0, utils_common_1.addLeadingSlash)((0, utils_common_1.addTrailingSlash)(value))); exports.DEFAULT_I18N_CONFIG = { defaultLocale: DEFAULT_I18N_LOCALE, path: utils_1.DEFAULT_I18N_DIR_NAME, locales: [DEFAULT_I18N_LOCALE], localeConfigs: {}, }; exports.DEFAULT_STORAGE_CONFIG = { type: 'localStorage', namespace: false, }; exports.DEFAULT_FASTER_CONFIG = { swcJsLoader: false, swcJsMinimizer: false, swcHtmlMinimizer: false, lightningCssMinimizer: false, mdxCrossCompilerCache: false, rspackBundler: false, rspackPersistentCache: false, ssgWorkerThreads: false, gitEagerVcs: false, }; // When using the "faster: true" shortcut exports.DEFAULT_FASTER_CONFIG_TRUE = { swcJsLoader: true, swcJsMinimizer: true, swcHtmlMinimizer: true, lightningCssMinimizer: true, mdxCrossCompilerCache: true, rspackBundler: true, rspackPersistentCache: true, ssgWorkerThreads: true, gitEagerVcs: true, }; exports.DEFAULT_FUTURE_V4_CONFIG = { removeLegacyPostBuildHeadAttribute: false, useCssCascadeLayers: false, siteStorageNamespacing: false, fasterByDefault: false, mdx1CompatDisabledByDefault: false, }; // When using the "v4: true" shortcut exports.DEFAULT_FUTURE_V4_CONFIG_TRUE = { removeLegacyPostBuildHeadAttribute: true, useCssCascadeLayers: true, siteStorageNamespacing: true, fasterByDefault: true, mdx1CompatDisabledByDefault: true, }; exports.DEFAULT_FUTURE_CONFIG = { v4: exports.DEFAULT_FUTURE_V4_CONFIG, faster: exports.DEFAULT_FASTER_CONFIG, experimental_vcs: (0, utils_1.getVcsPreset)('default-v1'), experimental_router: 'browser', }; exports.DEFAULT_MARKDOWN_HOOKS = { onBrokenMarkdownLinks: 'warn', onBrokenMarkdownImages: 'throw', }; exports.DEFAULT_MARKDOWN_MDX1COMPAT = { comments: true, admonitions: true, headingIds: true, }; exports.DEFAULT_MARKDOWN_CONFIG = { // TODO Docusaurus v5: change this to "detect"? // we probably need stable CommonMark support first // see https://github.com/facebook/docusaurus/issues/9092 format: 'mdx', mermaid: false, emoji: true, preprocessor: undefined, parseFrontMatter: utils_1.DEFAULT_PARSE_FRONT_MATTER, mdx1Compat: exports.DEFAULT_MARKDOWN_MDX1COMPAT, anchors: { maintainCase: false, }, remarkRehypeOptions: undefined, hooks: exports.DEFAULT_MARKDOWN_HOOKS, }; exports.DEFAULT_CONFIG = { i18n: exports.DEFAULT_I18N_CONFIG, storage: exports.DEFAULT_STORAGE_CONFIG, future: exports.DEFAULT_FUTURE_CONFIG, onBrokenLinks: 'throw', onBrokenAnchors: 'warn', // TODO Docusaurus v4: change to throw onBrokenMarkdownLinks: undefined, onDuplicateRoutes: 'warn', plugins: [], themes: [], presets: [], headTags: [], stylesheets: [], scripts: [], clientModules: [], customFields: {}, themeConfig: {}, titleDelimiter: '|', noIndex: false, tagline: '', baseUrlIssueBanner: true, staticDirectories: [utils_1.DEFAULT_STATIC_DIR_NAME], markdown: exports.DEFAULT_MARKDOWN_CONFIG, }; function createPluginSchema(theme) { return utils_validation_1.Joi.alternatives() .try(utils_validation_1.Joi.function(), utils_validation_1.Joi.array() .ordered(utils_validation_1.Joi.function().required(), utils_validation_1.Joi.object().required()) .length(2), utils_validation_1.Joi.string(), utils_validation_1.Joi.array() .ordered(utils_validation_1.Joi.string().required(), utils_validation_1.Joi.object().required()) .length(2), utils_validation_1.Joi.any().valid(false, null)) .error((errors) => { errors.forEach((error) => { const validConfigExample = theme ? `Example valid theme config: { themes: [ ["@docusaurus/theme-classic",options], "./myTheme", ["./myTheme",{someOption: 42}], function myTheme() { }, [function myTheme() { },options] ], };` : `Example valid plugin config: { plugins: [ ["@docusaurus/plugin-content-docs",options], "./myPlugin", ["./myPlugin",{someOption: 42}], function myPlugin() { }, [function myPlugin() { },options] ], };`; error.message = ` => Bad Docusaurus ${theme ? 'theme' : 'plugin'} value ${error.path.reduce((acc, cur) => typeof cur === 'string' ? `${acc}.${cur}` : `${acc}[${cur}]`)}. ${validConfigExample} `; }); return errors; }); } const PluginSchema = createPluginSchema(false); const ThemeSchema = createPluginSchema(true); const PresetSchema = utils_validation_1.Joi.alternatives() .try(utils_validation_1.Joi.string(), utils_validation_1.Joi.array() .items(utils_validation_1.Joi.string().required(), utils_validation_1.Joi.object().required()) .length(2), utils_validation_1.Joi.any().valid(false, null)) .messages({ 'alternatives.types': `{#label} does not look like a valid preset config. A preset config entry should be one of: - A tuple of [presetName, options], like \`["classic", \\{ blog: false \\}]\`, or - A simple string, like \`"classic"\``, }); const LocaleConfigSchema = utils_validation_1.Joi.object({ label: utils_validation_1.Joi.string(), htmlLang: utils_validation_1.Joi.string(), direction: utils_validation_1.Joi.string().equal('ltr', 'rtl'), calendar: utils_validation_1.Joi.string(), path: utils_validation_1.Joi.string(), translate: utils_validation_1.Joi.boolean(), url: SiteUrlSchema, baseUrl: BaseUrlSchema, }); const I18N_CONFIG_SCHEMA = utils_validation_1.Joi.object({ defaultLocale: utils_validation_1.Joi.string().required(), path: utils_validation_1.Joi.string().default(exports.DEFAULT_I18N_CONFIG.path), locales: utils_validation_1.Joi.array().items().min(1).items(utils_validation_1.Joi.string().required()).required(), localeConfigs: utils_validation_1.Joi.object() .pattern(/.*/, LocaleConfigSchema) .default(exports.DEFAULT_I18N_CONFIG.localeConfigs), }) .optional() .default(exports.DEFAULT_I18N_CONFIG); // Individual boolean defaults are not set here on purpose // They are resolved in postProcessDocusaurusConfig based on // the future.v4.fasterByDefault flag const FASTER_CONFIG_SCHEMA = utils_validation_1.Joi.alternatives() .try(utils_validation_1.Joi.object({ swcJsLoader: utils_validation_1.Joi.boolean(), swcJsMinimizer: utils_validation_1.Joi.boolean(), swcHtmlMinimizer: utils_validation_1.Joi.boolean(), lightningCssMinimizer: utils_validation_1.Joi.boolean(), mdxCrossCompilerCache: utils_validation_1.Joi.boolean(), rspackBundler: utils_validation_1.Joi.boolean(), rspackPersistentCache: utils_validation_1.Joi.boolean(), ssgWorkerThreads: utils_validation_1.Joi.boolean(), gitEagerVcs: utils_validation_1.Joi.boolean(), }), utils_validation_1.Joi.boolean() .required() .custom((bool) => bool ? exports.DEFAULT_FASTER_CONFIG_TRUE : exports.DEFAULT_FASTER_CONFIG)) .optional(); const FUTURE_V4_SCHEMA = utils_validation_1.Joi.alternatives() .try(utils_validation_1.Joi.object({ removeLegacyPostBuildHeadAttribute: utils_validation_1.Joi.boolean().default(exports.DEFAULT_FUTURE_V4_CONFIG.removeLegacyPostBuildHeadAttribute), useCssCascadeLayers: utils_validation_1.Joi.boolean().default(exports.DEFAULT_FUTURE_V4_CONFIG.useCssCascadeLayers), siteStorageNamespacing: utils_validation_1.Joi.boolean().default(exports.DEFAULT_FUTURE_V4_CONFIG.siteStorageNamespacing), fasterByDefault: utils_validation_1.Joi.boolean().default(exports.DEFAULT_FUTURE_V4_CONFIG.fasterByDefault), mdx1CompatDisabledByDefault: utils_validation_1.Joi.boolean().default(exports.DEFAULT_FUTURE_V4_CONFIG.mdx1CompatDisabledByDefault), }), utils_validation_1.Joi.boolean() .required() .custom((bool) => bool ? exports.DEFAULT_FUTURE_V4_CONFIG_TRUE : exports.DEFAULT_FUTURE_V4_CONFIG)) .optional() .default(exports.DEFAULT_FUTURE_V4_CONFIG); const STORAGE_CONFIG_SCHEMA = utils_validation_1.Joi.object({ type: utils_validation_1.Joi.string() .equal('localStorage', 'sessionStorage') .default(exports.DEFAULT_STORAGE_CONFIG.type), // namespace default is not set here on purpose // It is resolved in postProcessDocusaurusConfig based on // the future.v4.siteStorageNamespacing flag namespace: utils_validation_1.Joi.alternatives().try(utils_validation_1.Joi.string(), utils_validation_1.Joi.boolean()), }) .optional() .default({ type: exports.DEFAULT_STORAGE_CONFIG.type }); const VCS_CONFIG_OBJECT_SCHEMA = utils_validation_1.Joi.object({ // All the fields are required on purpose // You either provide a full VCS config or nothing initialize: utils_validation_1.Joi.function().maxArity(1).required(), getFileCreationInfo: utils_validation_1.Joi.function().arity(1).required(), getFileLastUpdateInfo: utils_validation_1.Joi.function().arity(1).required(), }); const VCS_CONFIG_SCHEMA = utils_validation_1.Joi.custom((input) => { if (typeof input === 'string') { const presetName = input; if (!utils_1.VcsPresetNames.includes(presetName)) { throw new Error(`VCS config preset name '${input}' is not valid.`); } return (0, utils_1.getVcsPreset)(presetName); } if (typeof input === 'boolean') { // We return the boolean on purpose // We'll normalize it to a real VcsConfig later // This is annoying, but we have to read the future flag to switch to the // new "default-v2" config (not easy to do it here) return input; } const { error, value } = VCS_CONFIG_OBJECT_SCHEMA.validate(input); if (error) { throw error; } return value; }).default(true); const FUTURE_CONFIG_SCHEMA = utils_validation_1.Joi.object({ v4: FUTURE_V4_SCHEMA, faster: FASTER_CONFIG_SCHEMA, experimental_vcs: VCS_CONFIG_SCHEMA, experimental_router: utils_validation_1.Joi.string() .equal('browser', 'hash') .default(exports.DEFAULT_FUTURE_CONFIG.experimental_router), experimental_storage: utils_validation_1.Joi.any() .forbidden() .messages({ 'any.unknown': `The Docusaurus config ${logger_1.default.code('future.experimental_storage')} has been promoted to a stable top-level ${logger_1.default.code('storage')} config attribute. Please move your storage config to the top level.`, }), experimental_faster: utils_validation_1.Joi.any() .forbidden() .messages({ 'any.unknown': `The Docusaurus config ${logger_1.default.code('future.experimental_faster')} has been renamed to ${logger_1.default.code('future.faster')}. Please update your Docusaurus config.`, }), }) .optional() .default(exports.DEFAULT_FUTURE_CONFIG); // TODO move to @docusaurus/utils-validation exports.ConfigSchema = utils_validation_1.Joi.object({ url: SiteUrlSchema.required(), baseUrl: BaseUrlSchema.required(), baseUrlIssueBanner: utils_validation_1.Joi.boolean().default(exports.DEFAULT_CONFIG.baseUrlIssueBanner), favicon: utils_validation_1.Joi.string().optional(), title: utils_validation_1.Joi.string().required(), trailingSlash: utils_validation_1.Joi.boolean(), // No default value! undefined = retrocompatible legacy behavior! i18n: I18N_CONFIG_SCHEMA, storage: STORAGE_CONFIG_SCHEMA, future: FUTURE_CONFIG_SCHEMA, onBrokenLinks: utils_validation_1.Joi.string() .equal('ignore', 'log', 'warn', 'throw') .default(exports.DEFAULT_CONFIG.onBrokenLinks), onBrokenAnchors: utils_validation_1.Joi.string() .equal('ignore', 'log', 'warn', 'throw') .default(exports.DEFAULT_CONFIG.onBrokenAnchors), onBrokenMarkdownLinks: utils_validation_1.Joi.string() .equal('ignore', 'log', 'warn', 'throw') .default(() => exports.DEFAULT_CONFIG.onBrokenMarkdownLinks), onDuplicateRoutes: utils_validation_1.Joi.string() .equal('ignore', 'log', 'warn', 'throw') .default(exports.DEFAULT_CONFIG.onDuplicateRoutes), organizationName: utils_validation_1.Joi.string().allow(''), staticDirectories: utils_validation_1.Joi.array() .items(utils_validation_1.Joi.string()) .default(exports.DEFAULT_CONFIG.staticDirectories), projectName: utils_validation_1.Joi.string().allow(''), deploymentBranch: utils_validation_1.Joi.string().optional(), customFields: utils_validation_1.Joi.object().unknown().default(exports.DEFAULT_CONFIG.customFields), githubHost: utils_validation_1.Joi.string(), githubPort: utils_validation_1.Joi.string(), plugins: utils_validation_1.Joi.array().items(PluginSchema).default(exports.DEFAULT_CONFIG.plugins), themes: utils_validation_1.Joi.array().items(ThemeSchema).default(exports.DEFAULT_CONFIG.themes), presets: utils_validation_1.Joi.array().items(PresetSchema).default(exports.DEFAULT_CONFIG.presets), themeConfig: utils_validation_1.Joi.object().unknown().default(exports.DEFAULT_CONFIG.themeConfig), scripts: utils_validation_1.Joi.array() .items(utils_validation_1.Joi.string(), utils_validation_1.Joi.object({ src: utils_validation_1.Joi.string().required(), async: utils_validation_1.Joi.bool(), defer: utils_validation_1.Joi.bool(), }) // See https://github.com/facebook/docusaurus/issues/3378 .unknown()) .messages({ 'array.includes': '{#label} is invalid. A script must be a plain string (the src), or an object with at least a "src" property.', }) .default(exports.DEFAULT_CONFIG.scripts), ssrTemplate: utils_validation_1.Joi.string(), headTags: utils_validation_1.Joi.array() .items(utils_validation_1.Joi.object({ tagName: utils_validation_1.Joi.string().required(), attributes: utils_validation_1.Joi.object().when('customElement', { is: utils_validation_1.Joi.valid(true), then: utils_validation_1.Joi.optional(), otherwise: utils_validation_1.Joi.object() .pattern(/[\w-]+/, utils_validation_1.Joi.alternatives().try(utils_validation_1.Joi.string(), utils_validation_1.Joi.boolean())) .required(), }), customElement: utils_validation_1.Joi.bool().default(false), }).unknown()) .messages({ 'array.includes': '{#label} is invalid. A headTag must be an object with at least a "tagName" and an "attributes" property.', }) .default(exports.DEFAULT_CONFIG.headTags), stylesheets: utils_validation_1.Joi.array() .items(utils_validation_1.Joi.string(), utils_validation_1.Joi.object({ href: utils_validation_1.Joi.string().required(), type: utils_validation_1.Joi.string(), }).unknown()) .messages({ 'array.includes': '{#label} is invalid. A stylesheet must be a plain string (the href), or an object with at least a "href" property.', }) .default(exports.DEFAULT_CONFIG.stylesheets), clientModules: utils_validation_1.Joi.array() .items(utils_validation_1.Joi.string()) .default(exports.DEFAULT_CONFIG.clientModules), tagline: utils_validation_1.Joi.string().allow('').default(exports.DEFAULT_CONFIG.tagline), titleDelimiter: utils_validation_1.Joi.string().default(exports.DEFAULT_CONFIG.titleDelimiter), noIndex: utils_validation_1.Joi.bool().default(exports.DEFAULT_CONFIG.noIndex), webpack: utils_validation_1.Joi.object({ jsLoader: utils_validation_1.Joi.alternatives() .try(utils_validation_1.Joi.string().equal('babel'), utils_validation_1.Joi.function()) .optional(), }).optional(), markdown: utils_validation_1.Joi.object({ format: utils_validation_1.Joi.string() .equal('mdx', 'md', 'detect') .default(exports.DEFAULT_CONFIG.markdown.format), parseFrontMatter: utils_validation_1.Joi.function().default(() => exports.DEFAULT_CONFIG.markdown.parseFrontMatter), mermaid: utils_validation_1.Joi.boolean().default(exports.DEFAULT_CONFIG.markdown.mermaid), emoji: utils_validation_1.Joi.boolean().default(exports.DEFAULT_CONFIG.markdown.emoji), preprocessor: utils_validation_1.Joi.function() .arity(1) .optional() .default(() => exports.DEFAULT_CONFIG.markdown.preprocessor), // Individual boolean defaults are not set here on purpose // They are resolved in postProcessDocusaurusConfig based on // the future.v4.mdx1CompatDisabledByDefault flag mdx1Compat: utils_validation_1.Joi.object({ comments: utils_validation_1.Joi.boolean(), admonitions: utils_validation_1.Joi.boolean(), headingIds: utils_validation_1.Joi.boolean(), }).default({}), remarkRehypeOptions: // add proper external options validation? // Not sure if it's a good idea, validation is likely to become stale // See https://github.com/remarkjs/remark-rehype#options utils_validation_1.Joi.object().unknown(), anchors: utils_validation_1.Joi.object({ maintainCase: utils_validation_1.Joi.boolean().default(exports.DEFAULT_CONFIG.markdown.anchors.maintainCase), }).default(exports.DEFAULT_CONFIG.markdown.anchors), hooks: utils_validation_1.Joi.object({ onBrokenMarkdownLinks: utils_validation_1.Joi.alternatives() .try(utils_validation_1.Joi.string().equal('ignore', 'log', 'warn', 'throw'), utils_validation_1.Joi.function()) .default(exports.DEFAULT_CONFIG.markdown.hooks.onBrokenMarkdownLinks), onBrokenMarkdownImages: utils_validation_1.Joi.alternatives() .try(utils_validation_1.Joi.string().equal('ignore', 'log', 'warn', 'throw'), utils_validation_1.Joi.function()) .default(exports.DEFAULT_CONFIG.markdown.hooks.onBrokenMarkdownImages), }).default(exports.DEFAULT_CONFIG.markdown.hooks), }).default({ ...exports.DEFAULT_CONFIG.markdown, mdx1Compat: { // erased on purpose, filled using postprocessing }, }), }).messages({ 'docusaurus.configValidationWarning': 'Docusaurus config validation warning. Field {#label}: {#warningMessage}', }); // Expressing this kind of logic in Joi is a pain // We also want to decouple logic from Joi: easier to remove it later! function postProcessDocusaurusConfig(config) { var _a; // Resolve storage.namespace based on the v4 future flag // undefined means "not explicitly set by user" if (config.storage.namespace === undefined) { config.storage.namespace = config.future.v4.siteStorageNamespacing; } // Resolve faster config based on the v4.fasterByDefault flag // undefined means "not explicitly set by user" if (config.future.faster === undefined) { config.future.faster = {}; } const fasterDefault = config.future.v4.fasterByDefault; const fasterKeys = Object.keys(exports.DEFAULT_FASTER_CONFIG); for (const key of fasterKeys) { if (config.future.faster[key] === undefined) { config.future.faster[key] = fasterDefault; } } // Resolve mdx1Compat config based on the v4.mdx1CompatDisabledByDefault flag // undefined means "not explicitly set by user" const mdx1CompatDefault = !config.future.v4.mdx1CompatDisabledByDefault; const mdx1CompatKeys = Object.keys(exports.DEFAULT_MARKDOWN_MDX1COMPAT); for (const key of mdx1CompatKeys) { (_a = config.markdown.mdx1Compat)[key] ?? (_a[key] = mdx1CompatDefault); } if (config.onBrokenMarkdownLinks) { logger_1.default.warn `The code=${'siteConfig.onBrokenMarkdownLinks'} config option is deprecated and will be removed in Docusaurus v4. Please migrate and move this option to code=${'siteConfig.markdown.hooks.onBrokenMarkdownLinks'} instead.`; // For v3 retro compatibility we use the old attribute over the new one config.markdown.hooks.onBrokenMarkdownLinks = config.onBrokenMarkdownLinks; // We erase the former one to ensure we don't use it anywhere config.onBrokenMarkdownLinks = undefined; } // We normalize the VCS config when using a boolean value if (typeof config.future.experimental_vcs === 'boolean') { const vcsConfig = config.future.experimental_vcs ? config.future.faster.gitEagerVcs ? (0, utils_1.getVcsPreset)('default-v2') : (0, utils_1.getVcsPreset)('default-v1') : (0, utils_1.getVcsPreset)('disabled'); config.future.experimental_vcs = vcsConfig; } if (config.future.faster.ssgWorkerThreads && !config.future.v4.removeLegacyPostBuildHeadAttribute) { throw new Error(`Docusaurus config ${logger_1.default.code('future.faster.ssgWorkerThreads')} requires the future flag ${logger_1.default.code('future.v4.removeLegacyPostBuildHeadAttribute')} to be turned on. If you use Docusaurus Faster, we recommend that you also activate Docusaurus v4 future flags: ${logger_1.default.code('{future: {v4: true}}')} All the v4 future flags are documented here: https://docusaurus.io/docs/api/docusaurus-config#future`); } if (config.future.faster.rspackPersistentCache && !config.future.faster.rspackBundler) { throw new Error(`Docusaurus config flag ${logger_1.default.code('future.faster.rspackPersistentCache')} requires the flag ${logger_1.default.code('future.faster.rspackBundler')} to be turned on.`); } } // TODO move to @docusaurus/utils-validation function validateConfig(config, siteConfigPath) { const { error, warning, value } = exports.ConfigSchema.validate(config, { abortEarly: false, }); (0, utils_validation_1.printWarning)(warning); if (error) { const unknownFields = error.details.reduce((formattedError, err) => { if (err.type === 'object.unknown') { return `${formattedError}"${err.path.reduce((acc, cur) => typeof cur === 'string' ? `${acc}.${cur}` : `${acc}[${cur}]`)}",`; } return formattedError; }, ''); let formattedError = error.details.reduce((accumulatedErr, err) => err.type !== 'object.unknown' ? `${accumulatedErr}${err.message}\n` : accumulatedErr, ''); formattedError = unknownFields ? `${formattedError}These field(s) (${unknownFields}) are not recognized in ${siteConfigPath}.\nIf you still want these fields to be in your configuration, put them in the "customFields" field.\nSee https://docusaurus.io/docs/api/docusaurus-config/#customfields` : formattedError; throw new Error(formattedError); } postProcessDocusaurusConfig(value); return value; }