mirror of
https://github.com/facebook/docusaurus.git
synced 2025-08-06 02:08:55 +02:00
refactor(client-redirects): migrate validation to validateOptions lifecycle (#6924)
This commit is contained in:
parent
68aaf9201f
commit
da9f38b748
8 changed files with 86 additions and 113 deletions
|
@ -154,7 +154,9 @@ declare module '@docusaurus/Link' {
|
||||||
readonly href?: string;
|
readonly href?: string;
|
||||||
readonly autoAddBaseUrl?: boolean;
|
readonly autoAddBaseUrl?: boolean;
|
||||||
|
|
||||||
// escape hatch in case broken links check is annoying for a specific link
|
/**
|
||||||
|
* escape hatch in case broken links check is annoying for a specific link
|
||||||
|
*/
|
||||||
readonly 'data-noBrokenLinkCheck'?: boolean;
|
readonly 'data-noBrokenLinkCheck'?: boolean;
|
||||||
};
|
};
|
||||||
export default function Link(props: Props): JSX.Element;
|
export default function Link(props: Props): JSX.Element;
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`normalizePluginOptions rejects bad createRedirects user inputs 1`] = `
|
|
||||||
"Invalid @docusaurus/plugin-client-redirects options: \\"createRedirects\\" must be of type function
|
|
||||||
{
|
|
||||||
\\"createRedirects\\": [
|
|
||||||
\\"bad\\",
|
|
||||||
\\"value\\"
|
|
||||||
]
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`normalizePluginOptions rejects bad fromExtensions user inputs 1`] = `
|
|
||||||
"Invalid @docusaurus/plugin-client-redirects options: \\"fromExtensions[0]\\" contains an invalid value
|
|
||||||
{
|
|
||||||
\\"fromExtensions\\": [
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
123,
|
|
||||||
true
|
|
||||||
]
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`normalizePluginOptions rejects bad toExtensions user inputs 1`] = `
|
|
||||||
"Invalid @docusaurus/plugin-client-redirects options: \\"toExtensions[0]\\" contains an invalid value
|
|
||||||
{
|
|
||||||
\\"toExtensions\\": [
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
123,
|
|
||||||
true
|
|
||||||
]
|
|
||||||
}"
|
|
||||||
`;
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`normalizePluginOptions rejects bad createRedirects user inputs 1`] = `"\\"createRedirects\\" must be of type function"`;
|
||||||
|
|
||||||
|
exports[`normalizePluginOptions rejects bad fromExtensions user inputs 1`] = `"\\"fromExtensions[0]\\" contains an invalid value"`;
|
||||||
|
|
||||||
|
exports[`normalizePluginOptions rejects bad toExtensions user inputs 1`] = `"\\"toExtensions[0]\\" contains an invalid value"`;
|
|
@ -7,8 +7,9 @@
|
||||||
|
|
||||||
import type {PluginContext} from '../types';
|
import type {PluginContext} from '../types';
|
||||||
import collectRedirects from '../collectRedirects';
|
import collectRedirects from '../collectRedirects';
|
||||||
import normalizePluginOptions from '../normalizePluginOptions';
|
import {validateOptions} from '../options';
|
||||||
import {removeTrailingSlash} from '@docusaurus/utils';
|
import {removeTrailingSlash} from '@docusaurus/utils';
|
||||||
|
import {normalizePluginOptions} from '@docusaurus/utils-validation';
|
||||||
import type {Options} from '@docusaurus/plugin-client-redirects';
|
import type {Options} from '@docusaurus/plugin-client-redirects';
|
||||||
|
|
||||||
function createTestPluginContext(
|
function createTestPluginContext(
|
||||||
|
@ -19,7 +20,7 @@ function createTestPluginContext(
|
||||||
outDir: '/tmp',
|
outDir: '/tmp',
|
||||||
baseUrl: 'https://docusaurus.io',
|
baseUrl: 'https://docusaurus.io',
|
||||||
relativeRoutesPaths,
|
relativeRoutesPaths,
|
||||||
options: normalizePluginOptions(options),
|
options: validateOptions({validate: normalizePluginOptions, options}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,32 +5,38 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import normalizePluginOptions, {
|
import {validateOptions, DEFAULT_OPTIONS} from '../options';
|
||||||
DefaultPluginOptions,
|
import {normalizePluginOptions} from '@docusaurus/utils-validation';
|
||||||
} from '../normalizePluginOptions';
|
import type {
|
||||||
import type {CreateRedirectsFnOption} from '@docusaurus/plugin-client-redirects';
|
CreateRedirectsFnOption,
|
||||||
|
Options,
|
||||||
|
} from '@docusaurus/plugin-client-redirects';
|
||||||
|
|
||||||
|
function testValidate(options: Options) {
|
||||||
|
return validateOptions({validate: normalizePluginOptions, options});
|
||||||
|
}
|
||||||
|
|
||||||
describe('normalizePluginOptions', () => {
|
describe('normalizePluginOptions', () => {
|
||||||
it('returns default options for undefined user options', () => {
|
it('returns default options for undefined user options', () => {
|
||||||
expect(normalizePluginOptions()).toEqual(DefaultPluginOptions);
|
expect(testValidate(undefined)).toEqual(DEFAULT_OPTIONS);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns default options for empty user options', () => {
|
it('returns default options for empty user options', () => {
|
||||||
expect(normalizePluginOptions()).toEqual(DefaultPluginOptions);
|
expect(testValidate(undefined)).toEqual(DEFAULT_OPTIONS);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('overrides one default options with valid user options', () => {
|
it('overrides one default options with valid user options', () => {
|
||||||
expect(
|
expect(
|
||||||
normalizePluginOptions({
|
testValidate({
|
||||||
toExtensions: ['html'],
|
toExtensions: ['html'],
|
||||||
}),
|
}),
|
||||||
).toEqual({...DefaultPluginOptions, toExtensions: ['html']});
|
).toEqual({...DEFAULT_OPTIONS, id: 'default', toExtensions: ['html']});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('overrides all default options with valid user options', () => {
|
it('overrides all default options with valid user options', () => {
|
||||||
const createRedirects: CreateRedirectsFnOption = (_routePath: string) => [];
|
const createRedirects: CreateRedirectsFnOption = (_routePath: string) => [];
|
||||||
expect(
|
expect(
|
||||||
normalizePluginOptions({
|
testValidate({
|
||||||
fromExtensions: ['exe', 'zip'],
|
fromExtensions: ['exe', 'zip'],
|
||||||
toExtensions: ['html'],
|
toExtensions: ['html'],
|
||||||
createRedirects,
|
createRedirects,
|
||||||
|
@ -47,7 +53,7 @@ describe('normalizePluginOptions', () => {
|
||||||
|
|
||||||
it('rejects bad fromExtensions user inputs', () => {
|
it('rejects bad fromExtensions user inputs', () => {
|
||||||
expect(() =>
|
expect(() =>
|
||||||
normalizePluginOptions({
|
testValidate({
|
||||||
fromExtensions: [null, undefined, 123, true] as unknown as string[],
|
fromExtensions: [null, undefined, 123, true] as unknown as string[],
|
||||||
}),
|
}),
|
||||||
).toThrowErrorMatchingSnapshot();
|
).toThrowErrorMatchingSnapshot();
|
||||||
|
@ -55,7 +61,7 @@ describe('normalizePluginOptions', () => {
|
||||||
|
|
||||||
it('rejects bad toExtensions user inputs', () => {
|
it('rejects bad toExtensions user inputs', () => {
|
||||||
expect(() =>
|
expect(() =>
|
||||||
normalizePluginOptions({
|
testValidate({
|
||||||
toExtensions: [null, undefined, 123, true] as unknown as string[],
|
toExtensions: [null, undefined, 123, true] as unknown as string[],
|
||||||
}),
|
}),
|
||||||
).toThrowErrorMatchingSnapshot();
|
).toThrowErrorMatchingSnapshot();
|
||||||
|
@ -63,7 +69,7 @@ describe('normalizePluginOptions', () => {
|
||||||
|
|
||||||
it('rejects bad createRedirects user inputs', () => {
|
it('rejects bad createRedirects user inputs', () => {
|
||||||
expect(() =>
|
expect(() =>
|
||||||
normalizePluginOptions({
|
testValidate({
|
||||||
createRedirects: ['bad', 'value'] as unknown as CreateRedirectsFnOption,
|
createRedirects: ['bad', 'value'] as unknown as CreateRedirectsFnOption,
|
||||||
}),
|
}),
|
||||||
).toThrowErrorMatchingSnapshot();
|
).toThrowErrorMatchingSnapshot();
|
|
@ -9,7 +9,6 @@ import type {LoadContext, Plugin, Props} from '@docusaurus/types';
|
||||||
import type {PluginContext, RedirectMetadata} from './types';
|
import type {PluginContext, RedirectMetadata} from './types';
|
||||||
import type {PluginOptions} from '@docusaurus/plugin-client-redirects';
|
import type {PluginOptions} from '@docusaurus/plugin-client-redirects';
|
||||||
|
|
||||||
import normalizePluginOptions from './normalizePluginOptions';
|
|
||||||
import collectRedirects from './collectRedirects';
|
import collectRedirects from './collectRedirects';
|
||||||
import writeRedirectFiles, {
|
import writeRedirectFiles, {
|
||||||
toRedirectFilesMetadata,
|
toRedirectFilesMetadata,
|
||||||
|
@ -19,12 +18,10 @@ import {removePrefix, addLeadingSlash} from '@docusaurus/utils';
|
||||||
|
|
||||||
export default function pluginClientRedirectsPages(
|
export default function pluginClientRedirectsPages(
|
||||||
context: LoadContext,
|
context: LoadContext,
|
||||||
opts: PluginOptions,
|
options: PluginOptions,
|
||||||
): Plugin<unknown> {
|
): Plugin<unknown> {
|
||||||
const {trailingSlash} = context.siteConfig;
|
const {trailingSlash} = context.siteConfig;
|
||||||
|
|
||||||
const options = normalizePluginOptions(opts);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: 'docusaurus-plugin-client-redirects',
|
name: 'docusaurus-plugin-client-redirects',
|
||||||
async postBuild(props: Props) {
|
async postBuild(props: Props) {
|
||||||
|
@ -53,3 +50,5 @@ export default function pluginClientRedirectsPages(
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export {validateOptions} from './options';
|
||||||
|
|
|
@ -1,59 +0,0 @@
|
||||||
/**
|
|
||||||
* 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 type {
|
|
||||||
PluginOptions,
|
|
||||||
Options as UserPluginOptions,
|
|
||||||
RedirectOption,
|
|
||||||
} from '@docusaurus/plugin-client-redirects';
|
|
||||||
import {Joi, PathnameSchema} from '@docusaurus/utils-validation';
|
|
||||||
import {DEFAULT_PLUGIN_ID} from '@docusaurus/utils';
|
|
||||||
|
|
||||||
export const DefaultPluginOptions: PluginOptions = {
|
|
||||||
id: DEFAULT_PLUGIN_ID, // TODO temporary
|
|
||||||
fromExtensions: [],
|
|
||||||
toExtensions: [],
|
|
||||||
redirects: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
const RedirectPluginOptionValidation = Joi.object<RedirectOption>({
|
|
||||||
to: PathnameSchema.required(),
|
|
||||||
from: Joi.alternatives().try(
|
|
||||||
PathnameSchema.required(),
|
|
||||||
Joi.array().items(PathnameSchema.required()),
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
const isString = Joi.string().required().not(null);
|
|
||||||
|
|
||||||
const UserOptionsSchema = Joi.object<UserPluginOptions>({
|
|
||||||
id: Joi.string().optional(), // TODO remove once validation migrated to new system
|
|
||||||
fromExtensions: Joi.array().items(isString),
|
|
||||||
toExtensions: Joi.array().items(isString),
|
|
||||||
redirects: Joi.array().items(RedirectPluginOptionValidation),
|
|
||||||
createRedirects: Joi.function().arity(1),
|
|
||||||
});
|
|
||||||
|
|
||||||
function validateUserOptions(userOptions: UserPluginOptions) {
|
|
||||||
const {error} = UserOptionsSchema.validate(userOptions, {
|
|
||||||
abortEarly: true,
|
|
||||||
allowUnknown: false,
|
|
||||||
});
|
|
||||||
if (error) {
|
|
||||||
throw new Error(
|
|
||||||
`Invalid @docusaurus/plugin-client-redirects options: ${error.message}
|
|
||||||
${JSON.stringify(userOptions, null, 2)}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function normalizePluginOptions(
|
|
||||||
userPluginOptions: UserPluginOptions = {},
|
|
||||||
): PluginOptions {
|
|
||||||
validateUserOptions(userPluginOptions);
|
|
||||||
return {...DefaultPluginOptions, ...userPluginOptions};
|
|
||||||
}
|
|
52
packages/docusaurus-plugin-client-redirects/src/options.ts
Normal file
52
packages/docusaurus-plugin-client-redirects/src/options.ts
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
/**
|
||||||
|
* 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 type {
|
||||||
|
PluginOptions,
|
||||||
|
RedirectOption,
|
||||||
|
} from '@docusaurus/plugin-client-redirects';
|
||||||
|
import type {
|
||||||
|
OptionValidationContext,
|
||||||
|
ValidationResult,
|
||||||
|
} from '@docusaurus/types';
|
||||||
|
import {Joi, PathnameSchema} from '@docusaurus/utils-validation';
|
||||||
|
|
||||||
|
export const DEFAULT_OPTIONS: Partial<PluginOptions> = {
|
||||||
|
fromExtensions: [],
|
||||||
|
toExtensions: [],
|
||||||
|
redirects: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const RedirectPluginOptionValidation = Joi.object<RedirectOption>({
|
||||||
|
to: PathnameSchema.required(),
|
||||||
|
from: Joi.alternatives().try(
|
||||||
|
PathnameSchema.required(),
|
||||||
|
Joi.array().items(PathnameSchema.required()),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
const isString = Joi.string().required().not(null);
|
||||||
|
|
||||||
|
const UserOptionsSchema = Joi.object<PluginOptions>({
|
||||||
|
fromExtensions: Joi.array()
|
||||||
|
.items(isString)
|
||||||
|
.default(DEFAULT_OPTIONS.fromExtensions),
|
||||||
|
toExtensions: Joi.array()
|
||||||
|
.items(isString)
|
||||||
|
.default(DEFAULT_OPTIONS.toExtensions),
|
||||||
|
redirects: Joi.array()
|
||||||
|
.items(RedirectPluginOptionValidation)
|
||||||
|
.default(DEFAULT_OPTIONS.redirects),
|
||||||
|
createRedirects: Joi.function().arity(1),
|
||||||
|
}).default(DEFAULT_OPTIONS);
|
||||||
|
|
||||||
|
export function validateOptions({
|
||||||
|
validate,
|
||||||
|
options: userOptions,
|
||||||
|
}: OptionValidationContext<PluginOptions>): ValidationResult<PluginOptions> {
|
||||||
|
return validate(UserOptionsSchema, userOptions);
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue