diff --git a/packages/docusaurus-module-type-aliases/src/index.d.ts b/packages/docusaurus-module-type-aliases/src/index.d.ts index 86c722759c05..31da0335637e 100644 --- a/packages/docusaurus-module-type-aliases/src/index.d.ts +++ b/packages/docusaurus-module-type-aliases/src/index.d.ts @@ -154,7 +154,9 @@ declare module '@docusaurus/Link' { readonly href?: string; 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; }; export default function Link(props: Props): JSX.Element; diff --git a/packages/docusaurus-plugin-client-redirects/src/__tests__/__snapshots__/normalizePluginOptions.test.ts.snap b/packages/docusaurus-plugin-client-redirects/src/__tests__/__snapshots__/normalizePluginOptions.test.ts.snap deleted file mode 100644 index e16490337bb1..000000000000 --- a/packages/docusaurus-plugin-client-redirects/src/__tests__/__snapshots__/normalizePluginOptions.test.ts.snap +++ /dev/null @@ -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 - ] -}" -`; diff --git a/packages/docusaurus-plugin-client-redirects/src/__tests__/__snapshots__/options.test.ts.snap b/packages/docusaurus-plugin-client-redirects/src/__tests__/__snapshots__/options.test.ts.snap new file mode 100644 index 000000000000..83f23180a018 --- /dev/null +++ b/packages/docusaurus-plugin-client-redirects/src/__tests__/__snapshots__/options.test.ts.snap @@ -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"`; diff --git a/packages/docusaurus-plugin-client-redirects/src/__tests__/collectRedirects.test.ts b/packages/docusaurus-plugin-client-redirects/src/__tests__/collectRedirects.test.ts index 2b7d12f1aa41..3e7f47e5f853 100644 --- a/packages/docusaurus-plugin-client-redirects/src/__tests__/collectRedirects.test.ts +++ b/packages/docusaurus-plugin-client-redirects/src/__tests__/collectRedirects.test.ts @@ -7,8 +7,9 @@ import type {PluginContext} from '../types'; import collectRedirects from '../collectRedirects'; -import normalizePluginOptions from '../normalizePluginOptions'; +import {validateOptions} from '../options'; import {removeTrailingSlash} from '@docusaurus/utils'; +import {normalizePluginOptions} from '@docusaurus/utils-validation'; import type {Options} from '@docusaurus/plugin-client-redirects'; function createTestPluginContext( @@ -19,7 +20,7 @@ function createTestPluginContext( outDir: '/tmp', baseUrl: 'https://docusaurus.io', relativeRoutesPaths, - options: normalizePluginOptions(options), + options: validateOptions({validate: normalizePluginOptions, options}), }; } diff --git a/packages/docusaurus-plugin-client-redirects/src/__tests__/normalizePluginOptions.test.ts b/packages/docusaurus-plugin-client-redirects/src/__tests__/options.test.ts similarity index 71% rename from packages/docusaurus-plugin-client-redirects/src/__tests__/normalizePluginOptions.test.ts rename to packages/docusaurus-plugin-client-redirects/src/__tests__/options.test.ts index da727e4ad258..8b7fb9142312 100644 --- a/packages/docusaurus-plugin-client-redirects/src/__tests__/normalizePluginOptions.test.ts +++ b/packages/docusaurus-plugin-client-redirects/src/__tests__/options.test.ts @@ -5,32 +5,38 @@ * LICENSE file in the root directory of this source tree. */ -import normalizePluginOptions, { - DefaultPluginOptions, -} from '../normalizePluginOptions'; -import type {CreateRedirectsFnOption} from '@docusaurus/plugin-client-redirects'; +import {validateOptions, DEFAULT_OPTIONS} from '../options'; +import {normalizePluginOptions} from '@docusaurus/utils-validation'; +import type { + CreateRedirectsFnOption, + Options, +} from '@docusaurus/plugin-client-redirects'; + +function testValidate(options: Options) { + return validateOptions({validate: normalizePluginOptions, options}); +} describe('normalizePluginOptions', () => { 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', () => { - expect(normalizePluginOptions()).toEqual(DefaultPluginOptions); + expect(testValidate(undefined)).toEqual(DEFAULT_OPTIONS); }); it('overrides one default options with valid user options', () => { expect( - normalizePluginOptions({ + testValidate({ toExtensions: ['html'], }), - ).toEqual({...DefaultPluginOptions, toExtensions: ['html']}); + ).toEqual({...DEFAULT_OPTIONS, id: 'default', toExtensions: ['html']}); }); it('overrides all default options with valid user options', () => { const createRedirects: CreateRedirectsFnOption = (_routePath: string) => []; expect( - normalizePluginOptions({ + testValidate({ fromExtensions: ['exe', 'zip'], toExtensions: ['html'], createRedirects, @@ -47,7 +53,7 @@ describe('normalizePluginOptions', () => { it('rejects bad fromExtensions user inputs', () => { expect(() => - normalizePluginOptions({ + testValidate({ fromExtensions: [null, undefined, 123, true] as unknown as string[], }), ).toThrowErrorMatchingSnapshot(); @@ -55,7 +61,7 @@ describe('normalizePluginOptions', () => { it('rejects bad toExtensions user inputs', () => { expect(() => - normalizePluginOptions({ + testValidate({ toExtensions: [null, undefined, 123, true] as unknown as string[], }), ).toThrowErrorMatchingSnapshot(); @@ -63,7 +69,7 @@ describe('normalizePluginOptions', () => { it('rejects bad createRedirects user inputs', () => { expect(() => - normalizePluginOptions({ + testValidate({ createRedirects: ['bad', 'value'] as unknown as CreateRedirectsFnOption, }), ).toThrowErrorMatchingSnapshot(); diff --git a/packages/docusaurus-plugin-client-redirects/src/index.ts b/packages/docusaurus-plugin-client-redirects/src/index.ts index 2fc739c4840a..d5a20c405935 100644 --- a/packages/docusaurus-plugin-client-redirects/src/index.ts +++ b/packages/docusaurus-plugin-client-redirects/src/index.ts @@ -9,7 +9,6 @@ import type {LoadContext, Plugin, Props} from '@docusaurus/types'; import type {PluginContext, RedirectMetadata} from './types'; import type {PluginOptions} from '@docusaurus/plugin-client-redirects'; -import normalizePluginOptions from './normalizePluginOptions'; import collectRedirects from './collectRedirects'; import writeRedirectFiles, { toRedirectFilesMetadata, @@ -19,12 +18,10 @@ import {removePrefix, addLeadingSlash} from '@docusaurus/utils'; export default function pluginClientRedirectsPages( context: LoadContext, - opts: PluginOptions, + options: PluginOptions, ): Plugin { const {trailingSlash} = context.siteConfig; - const options = normalizePluginOptions(opts); - return { name: 'docusaurus-plugin-client-redirects', async postBuild(props: Props) { @@ -53,3 +50,5 @@ export default function pluginClientRedirectsPages( }, }; } + +export {validateOptions} from './options'; diff --git a/packages/docusaurus-plugin-client-redirects/src/normalizePluginOptions.ts b/packages/docusaurus-plugin-client-redirects/src/normalizePluginOptions.ts deleted file mode 100644 index b838746e1efd..000000000000 --- a/packages/docusaurus-plugin-client-redirects/src/normalizePluginOptions.ts +++ /dev/null @@ -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({ - 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({ - 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}; -} diff --git a/packages/docusaurus-plugin-client-redirects/src/options.ts b/packages/docusaurus-plugin-client-redirects/src/options.ts new file mode 100644 index 000000000000..301c1d640dbc --- /dev/null +++ b/packages/docusaurus-plugin-client-redirects/src/options.ts @@ -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 = { + fromExtensions: [], + toExtensions: [], + redirects: [], +}; + +const RedirectPluginOptionValidation = Joi.object({ + 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({ + 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): ValidationResult { + return validate(UserOptionsSchema, userOptions); +}