@docusaurus/utils-validation 2.0.0-beta.ff31de0ff → 2.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/Joi.d.ts +1 -0
- package/lib/Joi.d.ts.map +1 -0
- package/lib/Joi.js +1 -0
- package/lib/Joi.js.map +1 -0
- package/lib/JoiFrontMatter.d.ts +18 -0
- package/lib/JoiFrontMatter.d.ts.map +1 -0
- package/lib/JoiFrontMatter.js +33 -0
- package/lib/JoiFrontMatter.js.map +1 -0
- package/lib/index.d.ts +4 -2
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +19 -5
- package/lib/index.js.map +1 -0
- package/lib/validationSchemas.d.ts +7 -1
- package/lib/validationSchemas.d.ts.map +1 -0
- package/lib/validationSchemas.js +72 -23
- package/lib/validationSchemas.js.map +1 -0
- package/lib/validationUtils.d.ts +20 -6
- package/lib/validationUtils.d.ts.map +1 -0
- package/lib/validationUtils.js +44 -69
- package/lib/validationUtils.js.map +1 -0
- package/package.json +8 -7
- package/src/JoiFrontMatter.ts +31 -0
- package/src/index.ts +17 -2
- package/src/validationSchemas.ts +85 -18
- package/src/validationUtils.ts +46 -84
- package/lib/.tsbuildinfo +0 -3945
- package/src/__tests__/__snapshots__/validationSchemas.test.ts.snap +0 -63
- package/src/__tests__/validationSchemas.test.ts +0 -131
- package/src/__tests__/validationUtils.test.ts +0 -64
- package/tsconfig.json +0 -9
package/src/validationSchemas.ts
CHANGED
|
@@ -4,51 +4,118 @@
|
|
|
4
4
|
* This source code is licensed under the MIT license found in the
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
|
+
|
|
8
|
+
import {isValidPathname, DEFAULT_PLUGIN_ID, type Tag} from '@docusaurus/utils';
|
|
7
9
|
import Joi from './Joi';
|
|
8
|
-
import {
|
|
10
|
+
import {JoiFrontMatter} from './JoiFrontMatter';
|
|
9
11
|
|
|
10
12
|
export const PluginIdSchema = Joi.string()
|
|
11
|
-
.regex(/^[
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
.regex(/^[\w-]+$/)
|
|
14
|
+
.message(
|
|
15
|
+
'Illegal plugin ID value "{#value}": it should only contain alphanumerics, underscores, and dashes.',
|
|
16
|
+
)
|
|
17
|
+
.default(DEFAULT_PLUGIN_ID);
|
|
14
18
|
|
|
15
19
|
const MarkdownPluginsSchema = Joi.array()
|
|
16
20
|
.items(
|
|
17
|
-
Joi.array().ordered(Joi.function().required(), Joi.
|
|
21
|
+
Joi.array().ordered(Joi.function().required(), Joi.any().required()),
|
|
18
22
|
Joi.function(),
|
|
19
23
|
Joi.object(),
|
|
20
24
|
)
|
|
25
|
+
.messages({
|
|
26
|
+
'array.includes': `{#label} does not look like a valid MDX plugin config. A plugin config entry should be one of:
|
|
27
|
+
- A tuple, like \`[require("rehype-katex"), \\{ strict: false \\}]\`, or
|
|
28
|
+
- A simple module, like \`require("remark-math")\``,
|
|
29
|
+
})
|
|
21
30
|
.default([]);
|
|
22
31
|
|
|
23
32
|
export const RemarkPluginsSchema = MarkdownPluginsSchema;
|
|
24
33
|
export const RehypePluginsSchema = MarkdownPluginsSchema;
|
|
25
34
|
|
|
26
|
-
|
|
35
|
+
const LegacyAdmonitionConfigSchema = Joi.forbidden().messages({
|
|
36
|
+
'any.unknown': `The Docusaurus admonitions system has changed, and the option {#label} does not exist anymore.
|
|
37
|
+
You now need to swizzle the admonitions component to provide UI customizations such as icons.
|
|
38
|
+
Please refer to https://github.com/facebook/docusaurus/pull/7152 for detailed upgrade instructions.`,
|
|
39
|
+
});
|
|
27
40
|
|
|
41
|
+
export const AdmonitionsSchema = JoiFrontMatter.alternatives()
|
|
42
|
+
.try(
|
|
43
|
+
JoiFrontMatter.boolean().required(),
|
|
44
|
+
JoiFrontMatter.object({
|
|
45
|
+
tag: JoiFrontMatter.string(),
|
|
46
|
+
keywords: JoiFrontMatter.array().items(
|
|
47
|
+
JoiFrontMatter.string().required(),
|
|
48
|
+
),
|
|
49
|
+
// TODO Remove before 2023
|
|
50
|
+
customTypes: LegacyAdmonitionConfigSchema,
|
|
51
|
+
icons: LegacyAdmonitionConfigSchema,
|
|
52
|
+
infima: LegacyAdmonitionConfigSchema,
|
|
53
|
+
}).required(),
|
|
54
|
+
)
|
|
55
|
+
.default(true)
|
|
56
|
+
.messages({
|
|
57
|
+
'alternatives.types':
|
|
58
|
+
'{{#label}} does not look like a valid admonitions config',
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// TODO how can we make this emit a custom error message :'(
|
|
62
|
+
// Joi is such a pain, good luck to annoying trying to improve this
|
|
28
63
|
export const URISchema = Joi.alternatives(
|
|
29
64
|
Joi.string().uri({allowRelative: true}),
|
|
30
|
-
|
|
65
|
+
// This custom validation logic is required notably because Joi does not
|
|
66
|
+
// accept paths like /a/b/c ...
|
|
67
|
+
Joi.custom((val: unknown, helpers) => {
|
|
31
68
|
try {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
} else {
|
|
36
|
-
return helpers.error('any.invalid');
|
|
37
|
-
}
|
|
69
|
+
// eslint-disable-next-line no-new
|
|
70
|
+
new URL(String(val));
|
|
71
|
+
return val;
|
|
38
72
|
} catch {
|
|
39
73
|
return helpers.error('any.invalid');
|
|
40
74
|
}
|
|
41
75
|
}),
|
|
42
|
-
)
|
|
76
|
+
).messages({
|
|
77
|
+
'alternatives.match':
|
|
78
|
+
"{{#label}} does not look like a valid url (value='{{.value}}')",
|
|
79
|
+
});
|
|
43
80
|
|
|
44
81
|
export const PathnameSchema = Joi.string()
|
|
45
|
-
.custom((val) => {
|
|
82
|
+
.custom((val: string) => {
|
|
46
83
|
if (!isValidPathname(val)) {
|
|
47
84
|
throw new Error();
|
|
48
|
-
} else {
|
|
49
|
-
return val;
|
|
50
85
|
}
|
|
86
|
+
return val;
|
|
51
87
|
})
|
|
52
88
|
.message(
|
|
53
|
-
'{{#label}} is not a valid pathname. Pathname should start with
|
|
89
|
+
'{{#label}} is not a valid pathname. Pathname should start with slash and not contain any domain or query string.',
|
|
54
90
|
);
|
|
91
|
+
|
|
92
|
+
const FrontMatterTagSchema = JoiFrontMatter.alternatives()
|
|
93
|
+
.try(
|
|
94
|
+
JoiFrontMatter.string().required(),
|
|
95
|
+
JoiFrontMatter.object<Tag>({
|
|
96
|
+
label: JoiFrontMatter.string().required(),
|
|
97
|
+
permalink: JoiFrontMatter.string().required(),
|
|
98
|
+
}).required(),
|
|
99
|
+
)
|
|
100
|
+
.messages({
|
|
101
|
+
'alternatives.match': '{{#label}} does not look like a valid tag',
|
|
102
|
+
'alternatives.types': '{{#label}} does not look like a valid tag',
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
export const FrontMatterTagsSchema = JoiFrontMatter.array()
|
|
106
|
+
.items(FrontMatterTagSchema)
|
|
107
|
+
.messages({
|
|
108
|
+
'array.base':
|
|
109
|
+
'{{#label}} does not look like a valid front matter Yaml array.',
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
export const FrontMatterTOCHeadingLevels = {
|
|
113
|
+
toc_min_heading_level: JoiFrontMatter.number().when('toc_max_heading_level', {
|
|
114
|
+
is: JoiFrontMatter.exist(),
|
|
115
|
+
then: JoiFrontMatter.number()
|
|
116
|
+
.min(2)
|
|
117
|
+
.max(JoiFrontMatter.ref('toc_max_heading_level')),
|
|
118
|
+
otherwise: JoiFrontMatter.number().min(2).max(6),
|
|
119
|
+
}),
|
|
120
|
+
toc_max_heading_level: JoiFrontMatter.number().min(2).max(6),
|
|
121
|
+
};
|
package/src/validationUtils.ts
CHANGED
|
@@ -4,106 +4,80 @@
|
|
|
4
4
|
* This source code is licensed under the MIT license found in the
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
|
-
import Joi from './Joi';
|
|
8
|
-
import chalk from 'chalk';
|
|
9
|
-
import {PluginIdSchema} from './validationSchemas';
|
|
10
7
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
// Undocumented on purpose, as we don't want users to keep using it over time
|
|
16
|
-
// Maybe we'll make this escape hatch official some day, with a better api?
|
|
17
|
-
export const isValidationDisabledEscapeHatch =
|
|
18
|
-
process.env.DISABLE_DOCUSAURUS_VALIDATION === 'true';
|
|
8
|
+
import logger from '@docusaurus/logger';
|
|
9
|
+
import Yaml from 'js-yaml';
|
|
10
|
+
import {PluginIdSchema} from './validationSchemas';
|
|
11
|
+
import type Joi from './Joi';
|
|
19
12
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
13
|
+
/** Print warnings returned from Joi validation. */
|
|
14
|
+
export function printWarning(warning?: Joi.ValidationError): void {
|
|
15
|
+
if (warning) {
|
|
16
|
+
const warningMessages = warning.details
|
|
17
|
+
.map(({message}) => message)
|
|
18
|
+
.join('\n');
|
|
19
|
+
logger.warn(warningMessages);
|
|
20
|
+
}
|
|
26
21
|
}
|
|
27
22
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
'\nIf you think your configuration is valid and should keep working, please open a bug report.',
|
|
34
|
-
)}\n`,
|
|
35
|
-
);
|
|
36
|
-
};
|
|
37
|
-
|
|
23
|
+
/**
|
|
24
|
+
* The callback that should be used to validate plugin options. Handles plugin
|
|
25
|
+
* IDs on a generic level: no matter what the schema declares, this callback
|
|
26
|
+
* would require a string ID or default to "default".
|
|
27
|
+
*/
|
|
38
28
|
export function normalizePluginOptions<T extends {id?: string}>(
|
|
39
29
|
schema: Joi.ObjectSchema<T>,
|
|
40
|
-
|
|
30
|
+
// This allows us to automatically normalize undefined to { id: "default" }
|
|
31
|
+
options: Partial<T> = {},
|
|
41
32
|
): T {
|
|
42
33
|
// All plugins can be provided an "id" option (multi-instance support)
|
|
43
34
|
// we add schema validation automatically
|
|
44
35
|
const finalSchema = schema.append({
|
|
45
36
|
id: PluginIdSchema,
|
|
46
37
|
});
|
|
47
|
-
const {error, value} = finalSchema.validate(options, {
|
|
38
|
+
const {error, warning, value} = finalSchema.validate(options, {
|
|
48
39
|
convert: false,
|
|
49
40
|
});
|
|
41
|
+
|
|
42
|
+
printWarning(warning);
|
|
43
|
+
|
|
50
44
|
if (error) {
|
|
51
|
-
|
|
52
|
-
if (isValidationDisabledEscapeHatch) {
|
|
53
|
-
console.error(error);
|
|
54
|
-
return options as T;
|
|
55
|
-
} else {
|
|
56
|
-
throw error;
|
|
57
|
-
}
|
|
45
|
+
throw error;
|
|
58
46
|
}
|
|
47
|
+
|
|
59
48
|
return value;
|
|
60
49
|
}
|
|
61
50
|
|
|
51
|
+
/**
|
|
52
|
+
* The callback that should be used to validate theme config. No matter what the
|
|
53
|
+
* schema declares, this callback would allow unknown attributes.
|
|
54
|
+
*/
|
|
62
55
|
export function normalizeThemeConfig<T>(
|
|
63
56
|
schema: Joi.ObjectSchema<T>,
|
|
64
57
|
themeConfig: Partial<T>,
|
|
65
58
|
): T {
|
|
66
|
-
// A theme should only validate
|
|
59
|
+
// A theme should only validate its "slice" of the full themeConfig,
|
|
67
60
|
// not the whole object, so we allow unknown attributes
|
|
68
61
|
// otherwise one theme would fail validating the data of another theme
|
|
69
62
|
const finalSchema = schema.unknown();
|
|
70
63
|
|
|
71
|
-
const {error, value} = finalSchema.validate(themeConfig, {
|
|
64
|
+
const {error, warning, value} = finalSchema.validate(themeConfig, {
|
|
72
65
|
convert: false,
|
|
73
66
|
});
|
|
74
67
|
|
|
68
|
+
printWarning(warning);
|
|
69
|
+
|
|
75
70
|
if (error) {
|
|
76
|
-
|
|
77
|
-
if (isValidationDisabledEscapeHatch) {
|
|
78
|
-
console.error(error);
|
|
79
|
-
return themeConfig as T;
|
|
80
|
-
} else {
|
|
81
|
-
throw error;
|
|
82
|
-
}
|
|
71
|
+
throw error;
|
|
83
72
|
}
|
|
84
73
|
return value;
|
|
85
74
|
}
|
|
86
75
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
// see https://github.com/facebook/docusaurus/issues/4642
|
|
91
|
-
// see https://github.com/sideway/joi/issues/1442#issuecomment-823997884
|
|
92
|
-
const JoiFrontMatterString: Joi.Extension = {
|
|
93
|
-
type: 'string',
|
|
94
|
-
base: Joi.string(),
|
|
95
|
-
// Fix Yaml that tries to auto-convert many things to string out of the box
|
|
96
|
-
prepare: (value) => {
|
|
97
|
-
if (typeof value === 'number' || value instanceof Date) {
|
|
98
|
-
return {value: value.toString()};
|
|
99
|
-
}
|
|
100
|
-
return {value};
|
|
101
|
-
},
|
|
102
|
-
};
|
|
103
|
-
export const JoiFrontMatter: typeof Joi = Joi.extend(JoiFrontMatterString);
|
|
104
|
-
|
|
76
|
+
/**
|
|
77
|
+
* Validate front matter with better error message
|
|
78
|
+
*/
|
|
105
79
|
export function validateFrontMatter<T>(
|
|
106
|
-
frontMatter:
|
|
80
|
+
frontMatter: {[key: string]: unknown},
|
|
107
81
|
schema: Joi.ObjectSchema<T>,
|
|
108
82
|
): T {
|
|
109
83
|
const {value, error, warning} = schema.validate(frontMatter, {
|
|
@@ -112,32 +86,20 @@ export function validateFrontMatter<T>(
|
|
|
112
86
|
abortEarly: false,
|
|
113
87
|
});
|
|
114
88
|
|
|
89
|
+
printWarning(warning);
|
|
90
|
+
|
|
115
91
|
if (error) {
|
|
116
|
-
const frontMatterString = JSON.stringify(frontMatter, null, 2);
|
|
117
92
|
const errorDetails = error.details;
|
|
118
93
|
const invalidFields = errorDetails.map(({path}) => path).join(', ');
|
|
119
|
-
const errorMessages = errorDetails
|
|
120
|
-
.map(({message}) => ` - ${message}`)
|
|
121
|
-
.join('\n');
|
|
122
94
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
)}\ncontains invalid values for field(s): ${invalidFields}.\n${errorMessages}\n`,
|
|
130
|
-
),
|
|
131
|
-
);
|
|
95
|
+
logger.error`The following front matter:
|
|
96
|
+
---
|
|
97
|
+
${Yaml.dump(frontMatter)}---
|
|
98
|
+
contains invalid values for field(s): code=${invalidFields}.
|
|
99
|
+
${errorDetails.map(({message}) => message)}
|
|
100
|
+
`;
|
|
132
101
|
throw error;
|
|
133
102
|
}
|
|
134
103
|
|
|
135
|
-
if (warning) {
|
|
136
|
-
const warningMessages = warning.details
|
|
137
|
-
.map(({message}) => message)
|
|
138
|
-
.join('\n');
|
|
139
|
-
console.log(chalk.yellow(warningMessages));
|
|
140
|
-
}
|
|
141
|
-
|
|
142
104
|
return value;
|
|
143
105
|
}
|