mirror of
https://github.com/facebook/docusaurus.git
synced 2025-08-06 10:20:09 +02:00
chore(v2): use joi for config validation (#2987)
* use joi for validation * fix theme validation * add test for required fields * format errors * a little better format errors * fix config file * try to rerun action
This commit is contained in:
parent
ec3c281952
commit
3213955e72
8 changed files with 325 additions and 102 deletions
|
@ -30,7 +30,8 @@
|
||||||
"url": "https://github.com/facebook/docusaurus/issues"
|
"url": "https://github.com/facebook/docusaurus/issues"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@docusaurus/module-type-aliases": "^2.0.0-alpha.58"
|
"@docusaurus/module-type-aliases": "^2.0.0-alpha.58",
|
||||||
|
"@types/hapi__joi": "^17.1.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.9.0",
|
"@babel/core": "^7.9.0",
|
||||||
|
@ -43,6 +44,7 @@
|
||||||
"@docusaurus/types": "^2.0.0-alpha.58",
|
"@docusaurus/types": "^2.0.0-alpha.58",
|
||||||
"@docusaurus/utils": "^2.0.0-alpha.58",
|
"@docusaurus/utils": "^2.0.0-alpha.58",
|
||||||
"@endiliey/static-site-generator-webpack-plugin": "^4.0.0",
|
"@endiliey/static-site-generator-webpack-plugin": "^4.0.0",
|
||||||
|
"@hapi/joi": "^17.1.1",
|
||||||
"@svgr/webpack": "^5.4.0",
|
"@svgr/webpack": "^5.4.0",
|
||||||
"babel-loader": "^8.1.0",
|
"babel-loader": "^8.1.0",
|
||||||
"babel-plugin-dynamic-import-node": "^2.3.0",
|
"babel-plugin-dynamic-import-node": "^2.3.0",
|
||||||
|
|
|
@ -18,7 +18,6 @@ module.exports = {
|
||||||
'@docusaurus/plugin-content-docs',
|
'@docusaurus/plugin-content-docs',
|
||||||
{
|
{
|
||||||
path: '../docs',
|
path: '../docs',
|
||||||
sidebarPath: require.resolve('./sidebars.js'),
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
'@docusaurus/plugin-content-pages',
|
'@docusaurus/plugin-content-pages',
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`loadConfig website with incomplete siteConfig 1`] = `
|
||||||
|
"\\"favicon\\" is required
|
||||||
|
\\"url\\" is required
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`loadConfig website with no siteConfig 1`] = `"docusaurus.config.js not found"`;
|
||||||
|
|
||||||
|
exports[`loadConfig website with useless field (wrong field) in siteConfig 1`] = `
|
||||||
|
"\\"favicon\\" is required
|
||||||
|
These field(s) [\\"useLessField\\",] are not recognized in docusaurus.config.js.
|
||||||
|
If you still want these fields to be in your configuration, put them in the 'customFields' attribute.
|
||||||
|
See https://v2.docusaurus.io/docs/docusaurus.config.js/#customfields"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`loadConfig website with valid siteConfig 1`] = `
|
||||||
|
Object {
|
||||||
|
"baseUrl": "/",
|
||||||
|
"customFields": Object {},
|
||||||
|
"favicon": "img/docusaurus.ico",
|
||||||
|
"organizationName": "endiliey",
|
||||||
|
"plugins": Array [
|
||||||
|
Array [
|
||||||
|
"@docusaurus/plugin-content-docs",
|
||||||
|
Object {
|
||||||
|
"path": "../docs",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@docusaurus/plugin-content-pages",
|
||||||
|
],
|
||||||
|
"projectName": "hello",
|
||||||
|
"tagline": "Hello World",
|
||||||
|
"themeConfig": Object {},
|
||||||
|
"themes": Array [],
|
||||||
|
"title": "Hello",
|
||||||
|
"url": "https://docusaurus.io",
|
||||||
|
}
|
||||||
|
`;
|
|
@ -0,0 +1,50 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`validateConfig throw error for baseUrl without trailing \`/\` 1`] = `
|
||||||
|
"\\"baseUrl\\" must be a string with a trailing \`/\`
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`validateConfig throw error for required fields 1`] = `
|
||||||
|
"\\"baseUrl\\" is required
|
||||||
|
\\"favicon\\" is required
|
||||||
|
\\"title\\" is required
|
||||||
|
\\"url\\" is required
|
||||||
|
\\"themes\\" must be an array
|
||||||
|
\\"scripts\\" must be an array
|
||||||
|
\\"stylesheets\\" must be an array
|
||||||
|
These field(s) [\\"invalid\\",\\"preset\\",] are not recognized in docusaurus.config.js.
|
||||||
|
If you still want these fields to be in your configuration, put them in the 'customFields' attribute.
|
||||||
|
See https://v2.docusaurus.io/docs/docusaurus.config.js/#customfields"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`validateConfig throw error for unknown field 1`] = `
|
||||||
|
"These field(s) [\\"invalid\\",] are not recognized in docusaurus.config.js.
|
||||||
|
If you still want these fields to be in your configuration, put them in the 'customFields' attribute.
|
||||||
|
See https://v2.docusaurus.io/docs/docusaurus.config.js/#customfields"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`validateConfig throw error if css doesn't have href 1`] = `
|
||||||
|
"\\"stylesheets[1]\\" does not match any of the allowed types
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`validateConfig throw error if plugins is not array 1`] = `
|
||||||
|
"\\"plugins\\" must be an array
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`validateConfig throw error if presets is not array 1`] = `
|
||||||
|
"\\"presets\\" must be an array
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`validateConfig throw error if scripts doesn't have src 1`] = `
|
||||||
|
"\\"scripts[1]\\" does not match any of the allowed types
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`validateConfig throw error if themes is not array 1`] = `
|
||||||
|
"\\"themes\\" must be an array
|
||||||
|
"
|
||||||
|
`;
|
|
@ -13,26 +13,7 @@ describe('loadConfig', () => {
|
||||||
const fixtures = path.join(__dirname, '__fixtures__');
|
const fixtures = path.join(__dirname, '__fixtures__');
|
||||||
const siteDir = path.join(fixtures, 'simple-site');
|
const siteDir = path.join(fixtures, 'simple-site');
|
||||||
const config = loadConfig(siteDir);
|
const config = loadConfig(siteDir);
|
||||||
expect(config).toMatchInlineSnapshot(
|
expect(config).toMatchSnapshot();
|
||||||
{
|
|
||||||
plugins: expect.any(Array),
|
|
||||||
},
|
|
||||||
`
|
|
||||||
Object {
|
|
||||||
"baseUrl": "/",
|
|
||||||
"customFields": Object {},
|
|
||||||
"favicon": "img/docusaurus.ico",
|
|
||||||
"organizationName": "endiliey",
|
|
||||||
"plugins": Any<Array>,
|
|
||||||
"projectName": "hello",
|
|
||||||
"tagline": "Hello World",
|
|
||||||
"themeConfig": Object {},
|
|
||||||
"themes": Array [],
|
|
||||||
"title": "Hello",
|
|
||||||
"url": "https://docusaurus.io",
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
);
|
|
||||||
expect(config).not.toEqual({});
|
expect(config).not.toEqual({});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -40,24 +21,20 @@ describe('loadConfig', () => {
|
||||||
const siteDir = path.join(__dirname, '__fixtures__', 'bad-site');
|
const siteDir = path.join(__dirname, '__fixtures__', 'bad-site');
|
||||||
expect(() => {
|
expect(() => {
|
||||||
loadConfig(siteDir);
|
loadConfig(siteDir);
|
||||||
}).toThrowErrorMatchingInlineSnapshot(
|
}).toThrowErrorMatchingSnapshot();
|
||||||
`"The required field(s) 'favicon', 'url' are missing from docusaurus.config.js"`,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('website with useless field (wrong field) in siteConfig', () => {
|
test('website with useless field (wrong field) in siteConfig', () => {
|
||||||
const siteDir = path.join(__dirname, '__fixtures__', 'wrong-site');
|
const siteDir = path.join(__dirname, '__fixtures__', 'wrong-site');
|
||||||
expect(() => {
|
expect(() => {
|
||||||
loadConfig(siteDir);
|
loadConfig(siteDir);
|
||||||
}).toThrowErrorMatchingInlineSnapshot(
|
}).toThrowErrorMatchingSnapshot();
|
||||||
`"The required field(s) 'favicon' are missing from docusaurus.config.js"`,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('website with no siteConfig', () => {
|
test('website with no siteConfig', () => {
|
||||||
const siteDir = path.join(__dirname, '__fixtures__', 'nonExisting');
|
const siteDir = path.join(__dirname, '__fixtures__', 'nonExisting');
|
||||||
expect(() => {
|
expect(() => {
|
||||||
loadConfig(siteDir);
|
loadConfig(siteDir);
|
||||||
}).toThrowErrorMatchingInlineSnapshot(`"docusaurus.config.js not found"`);
|
}).toThrowErrorMatchingSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,112 @@
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {DEFAULT_CONFIG, validateConfig} from '../configValidation';
|
||||||
|
import {DocusaurusConfig} from '@docusaurus/types';
|
||||||
|
|
||||||
|
const baseConfig = {
|
||||||
|
baseUrl: '/',
|
||||||
|
favicon: 'some.ico',
|
||||||
|
title: 'my site',
|
||||||
|
url: 'https://mysite.com',
|
||||||
|
};
|
||||||
|
|
||||||
|
const testConfig = (config) => validateConfig({...baseConfig, ...config});
|
||||||
|
|
||||||
|
describe('validateConfig', () => {
|
||||||
|
test('normalize config', () => {
|
||||||
|
const value = testConfig({});
|
||||||
|
expect(value).toEqual({
|
||||||
|
...DEFAULT_CONFIG,
|
||||||
|
...baseConfig,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('throw error for unknown field', () => {
|
||||||
|
expect(() => {
|
||||||
|
testConfig({
|
||||||
|
invalid: true,
|
||||||
|
});
|
||||||
|
}).toThrowErrorMatchingSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('throw error for baseUrl without trailing `/`', () => {
|
||||||
|
expect(() => {
|
||||||
|
testConfig({
|
||||||
|
baseUrl: 'noslash',
|
||||||
|
});
|
||||||
|
}).toThrowErrorMatchingSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('throw error if plugins is not array', () => {
|
||||||
|
expect(() => {
|
||||||
|
testConfig({
|
||||||
|
plugins: {},
|
||||||
|
});
|
||||||
|
}).toThrowErrorMatchingSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('throw error if themes is not array', () => {
|
||||||
|
expect(() => {
|
||||||
|
testConfig({
|
||||||
|
themes: {},
|
||||||
|
});
|
||||||
|
}).toThrowErrorMatchingSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('throw error if presets is not array', () => {
|
||||||
|
expect(() => {
|
||||||
|
testConfig({
|
||||||
|
presets: {},
|
||||||
|
});
|
||||||
|
}).toThrowErrorMatchingSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("throw error if scripts doesn't have src", () => {
|
||||||
|
expect(() => {
|
||||||
|
testConfig({
|
||||||
|
scripts: ['https://some.com', {}],
|
||||||
|
});
|
||||||
|
}).toThrowErrorMatchingSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("throw error if css doesn't have href", () => {
|
||||||
|
expect(() => {
|
||||||
|
testConfig({
|
||||||
|
stylesheets: ['https://somescript.com', {type: 'text/css'}],
|
||||||
|
});
|
||||||
|
}).toThrowErrorMatchingSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('custom field in config', () => {
|
||||||
|
const value = testConfig({
|
||||||
|
customFields: {
|
||||||
|
author: 'anshul',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(value).toEqual({
|
||||||
|
...DEFAULT_CONFIG,
|
||||||
|
...baseConfig,
|
||||||
|
customFields: {
|
||||||
|
author: 'anshul',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('throw error for required fields', () => {
|
||||||
|
expect(
|
||||||
|
() =>
|
||||||
|
validateConfig(({
|
||||||
|
invalid: true,
|
||||||
|
preset: {},
|
||||||
|
stylesheets: {},
|
||||||
|
themes: {},
|
||||||
|
scripts: {},
|
||||||
|
} as unknown) as DocusaurusConfig), // to fields not in the type
|
||||||
|
).toThrowErrorMatchingSnapshot();
|
||||||
|
});
|
||||||
|
});
|
|
@ -7,46 +7,10 @@
|
||||||
|
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import importFresh from 'import-fresh';
|
import importFresh from 'import-fresh';
|
||||||
import has from 'lodash.has';
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import {DocusaurusConfig} from '@docusaurus/types';
|
||||||
import {CONFIG_FILE_NAME} from '../constants';
|
import {CONFIG_FILE_NAME} from '../constants';
|
||||||
import {DocusaurusConfig, PluginConfig} from '@docusaurus/types';
|
import {validateConfig} from './configValidation';
|
||||||
|
|
||||||
const REQUIRED_FIELDS = ['baseUrl', 'favicon', 'title', 'url'];
|
|
||||||
|
|
||||||
const OPTIONAL_FIELDS = [
|
|
||||||
'organizationName',
|
|
||||||
'projectName',
|
|
||||||
'customFields',
|
|
||||||
'githubHost',
|
|
||||||
'plugins',
|
|
||||||
'themes',
|
|
||||||
'presets',
|
|
||||||
'themeConfig',
|
|
||||||
'scripts',
|
|
||||||
'stylesheets',
|
|
||||||
'tagline',
|
|
||||||
];
|
|
||||||
|
|
||||||
const DEFAULT_CONFIG: {
|
|
||||||
plugins: PluginConfig[];
|
|
||||||
themes: PluginConfig[];
|
|
||||||
customFields: {
|
|
||||||
[key: string]: unknown;
|
|
||||||
};
|
|
||||||
themeConfig: {
|
|
||||||
[key: string]: unknown;
|
|
||||||
};
|
|
||||||
} = {
|
|
||||||
plugins: [],
|
|
||||||
themes: [],
|
|
||||||
customFields: {},
|
|
||||||
themeConfig: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
function formatFields(fields: string[]): string {
|
|
||||||
return fields.map((field) => `'${field}'`).join(', ');
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function loadConfig(siteDir: string): DocusaurusConfig {
|
export default function loadConfig(siteDir: string): DocusaurusConfig {
|
||||||
const configPath = path.resolve(siteDir, CONFIG_FILE_NAME);
|
const configPath = path.resolve(siteDir, CONFIG_FILE_NAME);
|
||||||
|
@ -56,39 +20,5 @@ export default function loadConfig(siteDir: string): DocusaurusConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadedConfig = importFresh(configPath) as Partial<DocusaurusConfig>;
|
const loadedConfig = importFresh(configPath) as Partial<DocusaurusConfig>;
|
||||||
const missingFields = REQUIRED_FIELDS.filter(
|
return validateConfig(loadedConfig);
|
||||||
(field) => !has(loadedConfig, field),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (missingFields.length > 0) {
|
|
||||||
throw new Error(
|
|
||||||
`The required field(s) ${formatFields(
|
|
||||||
missingFields,
|
|
||||||
)} are missing from ${CONFIG_FILE_NAME}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge default config with loaded config.
|
|
||||||
const config: DocusaurusConfig = {
|
|
||||||
...DEFAULT_CONFIG,
|
|
||||||
...loadedConfig,
|
|
||||||
} as DocusaurusConfig;
|
|
||||||
|
|
||||||
// Don't allow unrecognized fields.
|
|
||||||
const allowedFields = [...REQUIRED_FIELDS, ...OPTIONAL_FIELDS];
|
|
||||||
const unrecognizedFields = Object.keys(config).filter(
|
|
||||||
(field) => !allowedFields.includes(field),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (unrecognizedFields && unrecognizedFields.length > 0) {
|
|
||||||
throw new Error(
|
|
||||||
`The field(s) ${formatFields(
|
|
||||||
unrecognizedFields,
|
|
||||||
)} are not recognized in ${CONFIG_FILE_NAME}.
|
|
||||||
If you still want these fields to be in your configuration, put them in the 'customFields' attribute.
|
|
||||||
See https://v2.docusaurus.io/docs/docusaurus.config.js/#customfields`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return config;
|
|
||||||
}
|
}
|
||||||
|
|
113
packages/docusaurus/src/server/configValidation.ts
Normal file
113
packages/docusaurus/src/server/configValidation.ts
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {PluginConfig, DocusaurusConfig} from '@docusaurus/types';
|
||||||
|
import Joi from '@hapi/joi';
|
||||||
|
import {CONFIG_FILE_NAME} from '../constants';
|
||||||
|
|
||||||
|
export const DEFAULT_CONFIG: {
|
||||||
|
plugins: PluginConfig[];
|
||||||
|
themes: PluginConfig[];
|
||||||
|
customFields: {
|
||||||
|
[key: string]: unknown;
|
||||||
|
};
|
||||||
|
themeConfig: {
|
||||||
|
[key: string]: unknown;
|
||||||
|
};
|
||||||
|
} = {
|
||||||
|
plugins: [],
|
||||||
|
themes: [],
|
||||||
|
customFields: {},
|
||||||
|
themeConfig: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const ConfigSchema = Joi.object({
|
||||||
|
baseUrl: Joi.string()
|
||||||
|
.required()
|
||||||
|
.regex(new RegExp('/$', 'm'))
|
||||||
|
.message('{{#label}} must be a string with a trailing `/`'),
|
||||||
|
favicon: Joi.string().required(),
|
||||||
|
title: Joi.string().required(),
|
||||||
|
url: Joi.string().uri().required(),
|
||||||
|
organizationName: Joi.string(),
|
||||||
|
projectName: Joi.string(),
|
||||||
|
customFields: Joi.object().unknown().default(DEFAULT_CONFIG.customFields),
|
||||||
|
githubHost: Joi.string(),
|
||||||
|
plugins: Joi.array()
|
||||||
|
.items(
|
||||||
|
Joi.alternatives().try(
|
||||||
|
Joi.string(),
|
||||||
|
Joi.array()
|
||||||
|
.items(Joi.string().required(), Joi.object().required())
|
||||||
|
.length(2),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.default(DEFAULT_CONFIG.plugins),
|
||||||
|
themes: Joi.array()
|
||||||
|
.items(
|
||||||
|
Joi.alternatives().try(
|
||||||
|
Joi.string(),
|
||||||
|
Joi.array()
|
||||||
|
.items(Joi.string().required(), Joi.object().required())
|
||||||
|
.length(2),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.default(DEFAULT_CONFIG.themes),
|
||||||
|
presets: Joi.array().items(
|
||||||
|
Joi.alternatives().try(
|
||||||
|
Joi.string(),
|
||||||
|
Joi.array().items(Joi.string(), Joi.object()).length(2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
themeConfig: Joi.object().unknown().default(DEFAULT_CONFIG.themeConfig),
|
||||||
|
scripts: Joi.array().items(
|
||||||
|
Joi.string(),
|
||||||
|
Joi.object({
|
||||||
|
src: Joi.string().required(),
|
||||||
|
async: Joi.bool(),
|
||||||
|
defer: Joi.bool(),
|
||||||
|
}).oxor('async', 'defer'),
|
||||||
|
),
|
||||||
|
stylesheets: Joi.array().items(
|
||||||
|
Joi.string(),
|
||||||
|
Joi.object({
|
||||||
|
href: Joi.string().uri().required(),
|
||||||
|
type: Joi.string().required(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
tagline: Joi.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export function validateConfig(
|
||||||
|
config: Partial<DocusaurusConfig>,
|
||||||
|
): DocusaurusConfig {
|
||||||
|
const {error, value} = ConfigSchema.validate(config, {
|
||||||
|
abortEarly: false,
|
||||||
|
});
|
||||||
|
if (error) {
|
||||||
|
const unknownFields = error.details.reduce((formatedError, err) => {
|
||||||
|
if (err.type === 'object.unknown') {
|
||||||
|
return `${formatedError}"${err.path}",`;
|
||||||
|
}
|
||||||
|
return formatedError;
|
||||||
|
}, '');
|
||||||
|
let formatedError = error.details.reduce(
|
||||||
|
(accumalatedErr, err) =>
|
||||||
|
err.type !== 'object.unknown'
|
||||||
|
? `${accumalatedErr}${err.message}\n`
|
||||||
|
: accumalatedErr,
|
||||||
|
'',
|
||||||
|
);
|
||||||
|
formatedError = unknownFields
|
||||||
|
? `${formatedError}These field(s) [${unknownFields}] are not recognized in ${CONFIG_FILE_NAME}.\nIf you still want these fields to be in your configuration, put them in the 'customFields' attribute.\nSee https://v2.docusaurus.io/docs/docusaurus.config.js/#customfields`
|
||||||
|
: formatedError;
|
||||||
|
throw new Error(formatedError);
|
||||||
|
} else {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue