diff --git a/packages/docusaurus-plugin-client-redirects/package.json b/packages/docusaurus-plugin-client-redirects/package.json index 8f2e1added41..47811a1d48e5 100644 --- a/packages/docusaurus-plugin-client-redirects/package.json +++ b/packages/docusaurus-plugin-client-redirects/package.json @@ -20,8 +20,7 @@ "eta": "^1.1.1", "fs-extra": "^8.1.0", "globby": "^10.0.1", - "lodash": "^4.17.15", - "yup": "^0.29.0" + "lodash": "^4.17.15" }, "peerDependencies": { "@docusaurus/core": "^2.0.0", @@ -30,8 +29,5 @@ }, "engines": { "node": ">=10.9.0" - }, - "devDependencies": { - "@types/yup": "^0.29.0" } } diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/validation.test.ts.snap b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/pluginOptionSchema.test.ts.snap similarity index 100% rename from packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/validation.test.ts.snap rename to packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/pluginOptionSchema.test.ts.snap diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts index 98e5dfb67c42..0bc9715fa076 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts @@ -9,7 +9,7 @@ import fs from 'fs-extra'; import path from 'path'; import pluginContentBlog from '../index'; import {DocusaurusConfig, LoadContext} from '@docusaurus/types'; -import {PluginOptionSchema} from '../validation'; +import {PluginOptionSchema} from '../pluginOptionSchema'; function validateAndNormalize(schema, options) { const {value, error} = schema.validate(options); diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/validation.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/pluginOptionSchema.test.ts similarity index 88% rename from packages/docusaurus-plugin-content-blog/src/__tests__/validation.test.ts rename to packages/docusaurus-plugin-content-blog/src/__tests__/pluginOptionSchema.test.ts index 7e3085cdec8a..a7889db6d129 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/validation.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/pluginOptionSchema.test.ts @@ -5,11 +5,11 @@ * LICENSE file in the root directory of this source tree. */ -import {PluginOptionSchema, DefaultOptions} from '../validation'; +import {PluginOptionSchema, DEFAULT_OPTIONS} from '../pluginOptionSchema'; test('normalize options', () => { const {value} = PluginOptionSchema.validate({}); - expect(value).toEqual(DefaultOptions); + expect(value).toEqual(DEFAULT_OPTIONS); }); test('validate options', () => { @@ -20,7 +20,7 @@ test('validate options', () => { routeBasePath: 'not_blog', }); expect(value).toEqual({ - ...DefaultOptions, + ...DEFAULT_OPTIONS, postsPerPage: 5, include: ['api/*', 'docs/*'], routeBasePath: 'not_blog', @@ -54,7 +54,7 @@ test('convert all feed type to array with other feed type', () => { feedOptions: {type: 'all'}, }); expect(value).toEqual({ - ...DefaultOptions, + ...DEFAULT_OPTIONS, feedOptions: {type: ['rss', 'atom']}, }); }); diff --git a/packages/docusaurus-plugin-content-blog/src/index.ts b/packages/docusaurus-plugin-content-blog/src/index.ts index 96c0a9cb5782..6e9bfe98e6f5 100644 --- a/packages/docusaurus-plugin-content-blog/src/index.ts +++ b/packages/docusaurus-plugin-content-blog/src/index.ts @@ -21,7 +21,7 @@ import { BlogPaginated, BlogPost, } from './types'; -import {PluginOptionSchema} from './validation'; +import {PluginOptionSchema} from './pluginOptionSchema'; import { LoadContext, PluginContentLoadedActions, diff --git a/packages/docusaurus-plugin-content-blog/src/validation.ts b/packages/docusaurus-plugin-content-blog/src/pluginOptionSchema.ts similarity index 63% rename from packages/docusaurus-plugin-content-blog/src/validation.ts rename to packages/docusaurus-plugin-content-blog/src/pluginOptionSchema.ts index 523864a976da..ea11aaa22d94 100644 --- a/packages/docusaurus-plugin-content-blog/src/validation.ts +++ b/packages/docusaurus-plugin-content-blog/src/pluginOptionSchema.ts @@ -7,7 +7,7 @@ import * as Joi from '@hapi/joi'; -export const DefaultOptions = { +export const DEFAULT_OPTIONS = { feedOptions: {}, beforeDefaultRehypePlugins: [], beforeDefaultRemarkPlugins: [], @@ -27,22 +27,22 @@ export const DefaultOptions = { }; export const PluginOptionSchema = Joi.object({ - path: Joi.string().default(DefaultOptions.path), - routeBasePath: Joi.string().default(DefaultOptions.routeBasePath), - include: Joi.array().items(Joi.string()).default(DefaultOptions.include), + path: Joi.string().default(DEFAULT_OPTIONS.path), + routeBasePath: Joi.string().default(DEFAULT_OPTIONS.routeBasePath), + include: Joi.array().items(Joi.string()).default(DEFAULT_OPTIONS.include), postsPerPage: Joi.number() .integer() .min(1) - .default(DefaultOptions.postsPerPage), - blogListComponent: Joi.string().default(DefaultOptions.blogListComponent), - blogPostComponent: Joi.string().default(DefaultOptions.blogPostComponent), + .default(DEFAULT_OPTIONS.postsPerPage), + blogListComponent: Joi.string().default(DEFAULT_OPTIONS.blogListComponent), + blogPostComponent: Joi.string().default(DEFAULT_OPTIONS.blogPostComponent), blogTagsListComponent: Joi.string().default( - DefaultOptions.blogTagsListComponent, + DEFAULT_OPTIONS.blogTagsListComponent, ), blogTagsPostsComponent: Joi.string().default( - DefaultOptions.blogTagsPostsComponent, + DEFAULT_OPTIONS.blogTagsPostsComponent, ), - showReadingTime: Joi.bool().default(DefaultOptions.showReadingTime), + showReadingTime: Joi.bool().default(DEFAULT_OPTIONS.showReadingTime), remarkPlugins: Joi.array() .items( Joi.alternatives().try( @@ -52,19 +52,19 @@ export const PluginOptionSchema = Joi.object({ .length(2), ), ) - .default(DefaultOptions.remarkPlugins), + .default(DEFAULT_OPTIONS.remarkPlugins), rehypePlugins: Joi.array() .items(Joi.string()) - .default(DefaultOptions.rehypePlugins), + .default(DEFAULT_OPTIONS.rehypePlugins), editUrl: Joi.string().uri(), - truncateMarker: Joi.object().default(DefaultOptions.truncateMarker), - admonitions: Joi.object().default(DefaultOptions.admonitions), + truncateMarker: Joi.object().default(DEFAULT_OPTIONS.truncateMarker), + admonitions: Joi.object().default(DEFAULT_OPTIONS.admonitions), beforeDefaultRemarkPlugins: Joi.array() .items(Joi.object()) - .default(DefaultOptions.beforeDefaultRemarkPlugins), + .default(DEFAULT_OPTIONS.beforeDefaultRemarkPlugins), beforeDefaultRehypePlugins: Joi.array() .items(Joi.object()) - .default(DefaultOptions.beforeDefaultRehypePlugins), + .default(DEFAULT_OPTIONS.beforeDefaultRehypePlugins), feedOptions: Joi.object({ type: Joi.alternatives().conditional( Joi.string().equal('all', 'rss', 'atom'), @@ -76,5 +76,5 @@ export const PluginOptionSchema = Joi.object({ description: Joi.string(), copyright: Joi.string(), language: Joi.string(), - }).default(DefaultOptions.feedOptions), + }).default(DEFAULT_OPTIONS.feedOptions), }); diff --git a/packages/docusaurus-plugin-content-docs/package.json b/packages/docusaurus-plugin-content-docs/package.json index cdae0b87f962..131cc9b2379f 100644 --- a/packages/docusaurus-plugin-content-docs/package.json +++ b/packages/docusaurus-plugin-content-docs/package.json @@ -13,7 +13,8 @@ "license": "MIT", "devDependencies": { "commander": "^5.0.0", - "picomatch": "^2.1.1" + "picomatch": "^2.1.1", + "@types/hapi__joi": "^17.1.2" }, "dependencies": { "@docusaurus/mdx-loader": "^2.0.0-alpha.58", @@ -22,6 +23,7 @@ "execa": "^3.4.0", "fs-extra": "^8.1.0", "globby": "^10.0.1", + "@hapi/joi": "17.1.1", "import-fresh": "^3.2.1", "loader-utils": "^1.2.3", "lodash.flatmap": "^4.5.0", diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/index.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/index.test.ts index 7edfe16efef0..1fd4d6a2be59 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/index.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/index.test.ts @@ -12,6 +12,7 @@ import commander from 'commander'; import fs from 'fs-extra'; import pluginContentDocs from '../index'; import loadEnv from '../env'; +import normalizePluginOptions from './pluginOptionSchema.test'; import {loadContext} from '@docusaurus/core/src/server/index'; import {applyConfigureWebpack} from '@docusaurus/core/src/webpack/utils'; import {RouteConfig} from '@docusaurus/types'; @@ -42,9 +43,12 @@ test('site with wrong sidebar file', async () => { const siteDir = path.join(__dirname, '__fixtures__', 'simple-site'); const context = loadContext(siteDir); const sidebarPath = path.join(siteDir, 'wrong-sidebars.json'); - const plugin = pluginContentDocs(context, { - sidebarPath, - }); + const plugin = pluginContentDocs( + context, + normalizePluginOptions({ + sidebarPath, + }), + ); await expect(plugin.loadContent()).rejects.toThrowErrorMatchingSnapshot(); }); @@ -54,7 +58,7 @@ describe('empty/no docs website', () => { test('no files in docs folder', async () => { await fs.ensureDir(path.join(siteDir, 'docs')); - const plugin = pluginContentDocs(context, {}); + const plugin = pluginContentDocs(context, normalizePluginOptions({})); const content = await plugin.loadContent(); const {docsMetadata, docsSidebars} = content; expect(docsMetadata).toMatchInlineSnapshot(`Object {}`); @@ -73,7 +77,12 @@ describe('empty/no docs website', () => { }); test('docs folder does not exist', async () => { - const plugin = pluginContentDocs(context, {path: '/path/does/not/exist/'}); + const plugin = pluginContentDocs( + context, + normalizePluginOptions({ + path: '/path/does/not/exist/', + }), + ); const content = await plugin.loadContent(); expect(content).toBeNull(); }); @@ -84,11 +93,14 @@ describe('simple website', () => { const context = loadContext(siteDir); const sidebarPath = path.join(siteDir, 'sidebars.json'); const pluginPath = 'docs'; - const plugin = pluginContentDocs(context, { - path: pluginPath, - sidebarPath, - homePageId: 'hello', - }); + const plugin = pluginContentDocs( + context, + normalizePluginOptions({ + path: pluginPath, + sidebarPath, + homePageId: 'hello', + }), + ); const pluginContentDir = path.join(context.generatedFilesDir, plugin.name); test('extendCli - docsVersion', () => { @@ -215,11 +227,14 @@ describe('versioned website', () => { const context = loadContext(siteDir); const sidebarPath = path.join(siteDir, 'sidebars.json'); const routeBasePath = 'docs'; - const plugin = pluginContentDocs(context, { - routeBasePath, - sidebarPath, - homePageId: 'hello', - }); + const plugin = pluginContentDocs( + context, + normalizePluginOptions({ + routeBasePath, + sidebarPath, + homePageId: 'hello', + }), + ); const env = loadEnv(siteDir); const {docsDir: versionedDir} = env.versioning; const pluginContentDir = path.join(context.generatedFilesDir, plugin.name); diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/pluginOptionSchema.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/pluginOptionSchema.test.ts new file mode 100644 index 000000000000..68cafdd88a20 --- /dev/null +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/pluginOptionSchema.test.ts @@ -0,0 +1,83 @@ +/** + * 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 {PluginOptionSchema, DEFAULT_OPTIONS} from '../pluginOptionSchema'; + +export default function normalizePluginOptions(options) { + const {value, error} = PluginOptionSchema.validate(options, { + convert: false, + }); + if (error) { + throw error; + } else { + return value; + } +} + +describe('normalizeDocsPluginOptions', () => { + test('should return default options for undefined user options', async () => { + const {value} = await PluginOptionSchema.validate({}); + expect(value).toEqual(DEFAULT_OPTIONS); + }); + + test('should accept correctly defined user options', async () => { + const userOptions = { + path: 'my-docs', // Path to data on filesystem, relative to site dir. + routeBasePath: 'my-docs', // URL Route. + homePageId: 'home', // Document id for docs home page. + include: ['**/*.{md,mdx}'], // Extensions to include. + sidebarPath: 'my-sidebar', // Path to sidebar configuration for showing a list of markdown pages. + docLayoutComponent: '@theme/DocPage', + docItemComponent: '@theme/DocItem', + remarkPlugins: [], + rehypePlugins: [], + showLastUpdateTime: true, + showLastUpdateAuthor: true, + admonitions: {}, + excludeNextVersionDocs: true, + }; + + const {value} = await PluginOptionSchema.validate(userOptions); + expect(value).toEqual(userOptions); + }); + + test('should reject bad path inputs', () => { + expect(() => { + normalizePluginOptions({ + path: 2, + }); + }).toThrowErrorMatchingInlineSnapshot(`"\\"path\\" must be a string"`); + }); + + test('should reject bad include inputs', () => { + expect(() => { + normalizePluginOptions({ + include: '**/*.{md,mdx}', + }); + }).toThrowErrorMatchingInlineSnapshot(`"\\"include\\" must be an array"`); + }); + + test('should reject bad showLastUpdateTime inputs', () => { + expect(() => { + normalizePluginOptions({ + showLastUpdateTime: 'true', + }); + }).toThrowErrorMatchingInlineSnapshot( + `"\\"showLastUpdateTime\\" must be a boolean"`, + ); + }); + + test('should reject bad remarkPlugins input', () => { + expect(() => { + normalizePluginOptions({ + remarkPlugins: 'remark-math', + }); + }).toThrowErrorMatchingInlineSnapshot( + `"\\"remarkPlugins\\" must be an array"`, + ); + }); +}); diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/sidebars.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/sidebars.test.ts index 2ae26ee777f2..1d322bba4463 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/sidebars.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/sidebars.test.ts @@ -24,7 +24,7 @@ describe('loadSidebars', () => { expect(result).toMatchSnapshot(); }); - test('sidebars shortand and longform lead to exact same sidebar', async () => { + test('sidebars shorthand and longform lead to exact same sidebar', async () => { const sidebarPath1 = path.join(fixtureDir, 'sidebars-category.js'); const sidebarPath2 = path.join( fixtureDir, diff --git a/packages/docusaurus-plugin-content-docs/src/index.ts b/packages/docusaurus-plugin-content-docs/src/index.ts index 473314397f61..e35f3c186613 100644 --- a/packages/docusaurus-plugin-content-docs/src/index.ts +++ b/packages/docusaurus-plugin-content-docs/src/index.ts @@ -18,7 +18,13 @@ import { objectWithKeySorted, aliasedSitePath, } from '@docusaurus/utils'; -import {LoadContext, Plugin, RouteConfig} from '@docusaurus/types'; +import { + LoadContext, + Plugin, + RouteConfig, + OptionValidationContext, + ValidationResult, +} from '@docusaurus/types'; import createOrder from './order'; import loadSidebars from './sidebars'; @@ -47,24 +53,8 @@ import { import {Configuration} from 'webpack'; import {docsVersion} from './version'; import {VERSIONS_JSON_FILE} from './constants'; - -const REVERSED_DOCS_HOME_PAGE_ID = '_index'; - -const DEFAULT_OPTIONS: PluginOptions = { - path: 'docs', // Path to data on filesystem, relative to site dir. - routeBasePath: 'docs', // URL Route. - homePageId: REVERSED_DOCS_HOME_PAGE_ID, // Document id for docs home page. - include: ['**/*.{md,mdx}'], // Extensions to include. - sidebarPath: '', // Path to sidebar configuration for showing a list of markdown pages. - docLayoutComponent: '@theme/DocPage', - docItemComponent: '@theme/DocItem', - remarkPlugins: [], - rehypePlugins: [], - showLastUpdateTime: false, - showLastUpdateAuthor: false, - admonitions: {}, - excludeNextVersionDocs: false, -}; +import {PluginOptionSchema} from './pluginOptionSchema'; +import {ValidationError} from '@hapi/joi'; function getFirstDocLinkOfSidebar( sidebarItems: DocsSidebarItem[], @@ -84,9 +74,8 @@ function getFirstDocLinkOfSidebar( export default function pluginContentDocs( context: LoadContext, - opts: Partial, -): Plugin { - const options: PluginOptions = {...DEFAULT_OPTIONS, ...opts}; + options: PluginOptions, +): Plugin { const homePageDocsRoutePath = options.routeBasePath === '' ? '/' : options.routeBasePath; @@ -551,3 +540,14 @@ Available document ids= }, }; } + +export function validateOptions({ + validate, + options, +}: OptionValidationContext): ValidationResult< + PluginOptions, + ValidationError +> { + const validatedOptions = validate(PluginOptionSchema, options); + return validatedOptions; +} diff --git a/packages/docusaurus-plugin-content-docs/src/pluginOptionSchema.ts b/packages/docusaurus-plugin-content-docs/src/pluginOptionSchema.ts new file mode 100644 index 000000000000..95edd762c5df --- /dev/null +++ b/packages/docusaurus-plugin-content-docs/src/pluginOptionSchema.ts @@ -0,0 +1,54 @@ +/** + * 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 * as Joi from '@hapi/joi'; +import {PluginOptions} from './types'; + +const REVERSED_DOCS_HOME_PAGE_ID = '_index'; + +export const DEFAULT_OPTIONS: PluginOptions = { + path: 'docs', // Path to data on filesystem, relative to site dir. + routeBasePath: 'docs', // URL Route. + homePageId: REVERSED_DOCS_HOME_PAGE_ID, // Document id for docs home page. + include: ['**/*.{md,mdx}'], // Extensions to include. + sidebarPath: '', // Path to sidebar configuration for showing a list of markdown pages. + docLayoutComponent: '@theme/DocPage', + docItemComponent: '@theme/DocItem', + remarkPlugins: [], + rehypePlugins: [], + showLastUpdateTime: false, + showLastUpdateAuthor: false, + admonitions: {}, + excludeNextVersionDocs: false, +}; + +export const PluginOptionSchema = Joi.object({ + path: Joi.string().default(DEFAULT_OPTIONS.path), + editUrl: Joi.string().uri(), + routeBasePath: Joi.string().default(DEFAULT_OPTIONS.routeBasePath), + homePageId: Joi.string().default(DEFAULT_OPTIONS.homePageId), + include: Joi.array().items(Joi.string()).default(DEFAULT_OPTIONS.include), + sidebarPath: Joi.string().default(DEFAULT_OPTIONS.sidebarPath), + docLayoutComponent: Joi.string().default(DEFAULT_OPTIONS.docLayoutComponent), + docItemComponent: Joi.string().default(DEFAULT_OPTIONS.docItemComponent), + remarkPlugins: Joi.array() + .items( + Joi.array().items(Joi.function(), Joi.object()).length(2), + Joi.function(), + ) + .default(DEFAULT_OPTIONS.remarkPlugins), + rehypePlugins: Joi.array() + .items(Joi.string()) + .default(DEFAULT_OPTIONS.rehypePlugins), + showLastUpdateTime: Joi.bool().default(DEFAULT_OPTIONS.showLastUpdateTime), + showLastUpdateAuthor: Joi.bool().default( + DEFAULT_OPTIONS.showLastUpdateAuthor, + ), + admonitions: Joi.object().default(DEFAULT_OPTIONS.admonitions), + excludeNextVersionDocs: Joi.bool().default( + DEFAULT_OPTIONS.excludeNextVersionDocs, + ), +}); diff --git a/packages/docusaurus-plugin-content-pages/package.json b/packages/docusaurus-plugin-content-pages/package.json index 12670bdefb13..13eb71e26f34 100644 --- a/packages/docusaurus-plugin-content-pages/package.json +++ b/packages/docusaurus-plugin-content-pages/package.json @@ -11,10 +11,14 @@ "access": "public" }, "license": "MIT", + "devDependencies": { + "@types/hapi__joi": "^17.1.2" + }, "dependencies": { "@docusaurus/types": "^2.0.0-alpha.58", "@docusaurus/utils": "^2.0.0-alpha.58", - "globby": "^10.0.1" + "globby": "^10.0.1", + "@hapi/joi": "17.1.1" }, "peerDependencies": { "@docusaurus/core": "^2.0.0", diff --git a/packages/docusaurus-plugin-content-pages/src/__tests__/index.test.ts b/packages/docusaurus-plugin-content-pages/src/__tests__/index.test.ts index f79fa915246a..b5d34c9eafb7 100644 --- a/packages/docusaurus-plugin-content-pages/src/__tests__/index.test.ts +++ b/packages/docusaurus-plugin-content-pages/src/__tests__/index.test.ts @@ -9,6 +9,7 @@ import path from 'path'; import pluginContentPages from '../index'; import {LoadContext} from '@docusaurus/types'; +import normalizePluginOptions from './pluginOptionSchema.test'; describe('docusaurus-plugin-content-pages', () => { test('simple pages', async () => { @@ -23,9 +24,12 @@ describe('docusaurus-plugin-content-pages', () => { siteConfig, } as LoadContext; const pluginPath = 'src/pages'; - const plugin = pluginContentPages(context, { - path: pluginPath, - }); + const plugin = pluginContentPages( + context, + normalizePluginOptions({ + path: pluginPath, + }), + ); const pagesMetadatas = await plugin.loadContent(); expect(pagesMetadatas).toEqual([ diff --git a/packages/docusaurus-plugin-content-pages/src/__tests__/pluginOptionSchema.test.ts b/packages/docusaurus-plugin-content-pages/src/__tests__/pluginOptionSchema.test.ts new file mode 100644 index 000000000000..b117e6b0571a --- /dev/null +++ b/packages/docusaurus-plugin-content-pages/src/__tests__/pluginOptionSchema.test.ts @@ -0,0 +1,49 @@ +/** + * 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 {PluginOptionSchema, DEFAULT_OPTIONS} from '../pluginOptionSchema'; + +export default function normalizePluginOptions(options) { + const {value, error} = PluginOptionSchema.validate(options, { + convert: false, + }); + if (error) { + throw error; + } else { + return value; + } +} + +describe('normalizePagesPluginOptions', () => { + test('should return default options for undefined user options', async () => { + const {value} = await PluginOptionSchema.validate({}); + expect(value).toEqual(DEFAULT_OPTIONS); + }); + + test('should fill in default options for partially defined user options', async () => { + const {value} = await PluginOptionSchema.validate({path: 'src/pages'}); + expect(value).toEqual(DEFAULT_OPTIONS); + }); + + test('should accept correctly defined user options', async () => { + const userOptions = { + path: 'src/my-pages', + routeBasePath: 'my-pages', + include: ['**/*.{js,jsx,ts,tsx}'], + }; + const {value} = await PluginOptionSchema.validate(userOptions); + expect(value).toEqual(userOptions); + }); + + test('should reject bad path inputs', () => { + expect(() => { + normalizePluginOptions({ + path: 42, + }); + }).toThrowErrorMatchingInlineSnapshot(`"\\"path\\" must be a string"`); + }); +}); diff --git a/packages/docusaurus-plugin-content-pages/src/index.ts b/packages/docusaurus-plugin-content-pages/src/index.ts index bf9f974221b7..0efea4c91483 100644 --- a/packages/docusaurus-plugin-content-pages/src/index.ts +++ b/packages/docusaurus-plugin-content-pages/src/index.ts @@ -9,21 +9,21 @@ import globby from 'globby'; import fs from 'fs'; import path from 'path'; import {encodePath, fileToPath, aliasedSitePath} from '@docusaurus/utils'; -import {LoadContext, Plugin} from '@docusaurus/types'; +import { + LoadContext, + Plugin, + OptionValidationContext, + ValidationResult, +} from '@docusaurus/types'; import {PluginOptions, LoadedContent} from './types'; - -const DEFAULT_OPTIONS: PluginOptions = { - path: 'src/pages', // Path to data on filesystem, relative to site dir. - routeBasePath: '', // URL Route. - include: ['**/*.{js,jsx,ts,tsx}'], // Extensions to include. -}; +import {PluginOptionSchema} from './pluginOptionSchema'; +import {ValidationError} from '@hapi/joi'; export default function pluginContentPages( context: LoadContext, - opts: Partial, -): Plugin { - const options = {...DEFAULT_OPTIONS, ...opts}; + options: PluginOptions, +): Plugin { const contentPath = path.resolve(context.siteDir, options.path); return { @@ -81,3 +81,14 @@ export default function pluginContentPages( }, }; } + +export function validateOptions({ + validate, + options, +}: OptionValidationContext): ValidationResult< + PluginOptions, + ValidationError +> { + const validatedOptions = validate(PluginOptionSchema, options); + return validatedOptions; +} diff --git a/packages/docusaurus-plugin-content-pages/src/pluginOptionSchema.ts b/packages/docusaurus-plugin-content-pages/src/pluginOptionSchema.ts new file mode 100644 index 000000000000..6d20134f00cb --- /dev/null +++ b/packages/docusaurus-plugin-content-pages/src/pluginOptionSchema.ts @@ -0,0 +1,20 @@ +/** + * 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 * as Joi from '@hapi/joi'; +import {PluginOptions} from './types'; + +export const DEFAULT_OPTIONS: PluginOptions = { + path: 'src/pages', // Path to data on filesystem, relative to site dir. + routeBasePath: '', // URL Route. + include: ['**/*.{js,jsx,ts,tsx}'], // Extensions to include. +}; + +export const PluginOptionSchema = Joi.object({ + path: Joi.string().default(DEFAULT_OPTIONS.path), + routeBasePath: Joi.string().default(DEFAULT_OPTIONS.routeBasePath), + include: Joi.array().items(Joi.string()).default(DEFAULT_OPTIONS.include), +}); diff --git a/packages/docusaurus-plugin-sitemap/package.json b/packages/docusaurus-plugin-sitemap/package.json index 3f6b938e474c..0515f6fb464e 100644 --- a/packages/docusaurus-plugin-sitemap/package.json +++ b/packages/docusaurus-plugin-sitemap/package.json @@ -11,10 +11,14 @@ "access": "public" }, "license": "MIT", + "devDependencies": { + "@types/hapi__joi": "^17.1.2" + }, "dependencies": { "@docusaurus/types": "^2.0.0-alpha.58", "fs-extra": "^8.1.0", - "sitemap": "^3.2.2" + "sitemap": "^3.2.2", + "@hapi/joi": "17.1.1" }, "peerDependencies": { "@docusaurus/core": "^2.0.0" diff --git a/packages/docusaurus-plugin-sitemap/src/__tests__/pluginOptionSchema.test.ts b/packages/docusaurus-plugin-sitemap/src/__tests__/pluginOptionSchema.test.ts new file mode 100644 index 000000000000..2fac06904827 --- /dev/null +++ b/packages/docusaurus-plugin-sitemap/src/__tests__/pluginOptionSchema.test.ts @@ -0,0 +1,64 @@ +/** + * 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 {PluginOptionSchema, DEFAULT_OPTIONS} from '../pluginOptionSchema'; + +function normalizePluginOptions(options) { + const {value, error} = PluginOptionSchema.validate(options, { + convert: false, + }); + if (error) { + throw error; + } else { + return value; + } +} + +describe('normalizeSitemapPluginOptions', () => { + test('should return default values for empty user options', async () => { + const {value} = await PluginOptionSchema.validate({}); + expect(value).toEqual(DEFAULT_OPTIONS); + }); + + test('should accept correctly defined user options', async () => { + const userOptions = { + cacheTime: 300, + changefreq: 'yearly', + priority: 0.9, + }; + const {value} = await PluginOptionSchema.validate(userOptions); + expect(value).toEqual(userOptions); + }); + + test('should reject cacheTime inputs with wrong type', () => { + expect(() => { + normalizePluginOptions({ + cacheTime: '42', + }); + }).toThrowErrorMatchingInlineSnapshot(`"\\"cacheTime\\" must be a number"`); + }); + + test('should reject out-of-range priority inputs', () => { + expect(() => { + normalizePluginOptions({ + priority: 2, + }); + }).toThrowErrorMatchingInlineSnapshot( + `"\\"priority\\" must be less than or equal to 1"`, + ); + }); + + test('should reject bad changefreq inputs', () => { + expect(() => { + normalizePluginOptions({ + changefreq: 'annually', + }); + }).toThrowErrorMatchingInlineSnapshot( + `"\\"changefreq\\" must be one of [always, hourly, daily, weekly, monthly, yearly, never]"`, + ); + }); +}); diff --git a/packages/docusaurus-plugin-sitemap/src/index.ts b/packages/docusaurus-plugin-sitemap/src/index.ts index c72ec4132135..ee891c268f3d 100644 --- a/packages/docusaurus-plugin-sitemap/src/index.ts +++ b/packages/docusaurus-plugin-sitemap/src/index.ts @@ -9,20 +9,20 @@ import fs from 'fs-extra'; import path from 'path'; import {PluginOptions} from './types'; import createSitemap from './createSitemap'; -import {LoadContext, Props, Plugin} from '@docusaurus/types'; - -const DEFAULT_OPTIONS: Required = { - cacheTime: 600 * 1000, // 600 sec - cache purge period. - changefreq: 'weekly', - priority: 0.5, -}; +import { + LoadContext, + Props, + OptionValidationContext, + ValidationResult, + Plugin, +} from '@docusaurus/types'; +import {PluginOptionSchema} from './pluginOptionSchema'; +import {ValidationError} from '@hapi/joi'; export default function pluginSitemap( _context: LoadContext, - opts: Partial, + options: PluginOptions, ): Plugin { - const options = {...DEFAULT_OPTIONS, ...opts}; - return { name: 'docusaurus-plugin-sitemap', @@ -44,3 +44,14 @@ export default function pluginSitemap( }, }; } + +export function validateOptions({ + validate, + options, +}: OptionValidationContext): ValidationResult< + PluginOptions, + ValidationError +> { + const validatedOptions = validate(PluginOptionSchema, options); + return validatedOptions; +} diff --git a/packages/docusaurus-plugin-sitemap/src/pluginOptionSchema.ts b/packages/docusaurus-plugin-sitemap/src/pluginOptionSchema.ts new file mode 100644 index 000000000000..303449bf356d --- /dev/null +++ b/packages/docusaurus-plugin-sitemap/src/pluginOptionSchema.ts @@ -0,0 +1,22 @@ +/** + * 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 * as Joi from '@hapi/joi'; +import {PluginOptions} from './types'; + +export const DEFAULT_OPTIONS: Required = { + cacheTime: 600 * 1000, // 600 sec - cache purge period. + changefreq: 'weekly', + priority: 0.5, +}; + +export const PluginOptionSchema = Joi.object({ + cacheTime: Joi.number().positive().default(DEFAULT_OPTIONS.cacheTime), + changefreq: Joi.string() + .valid('always', 'hourly', 'daily', 'weekly', 'monthly', 'yearly', 'never') + .default(DEFAULT_OPTIONS.changefreq), + priority: Joi.number().min(0).max(1).default(DEFAULT_OPTIONS.priority), +}); diff --git a/packages/docusaurus-theme-live-codeblock/package.json b/packages/docusaurus-theme-live-codeblock/package.json index 5ea8d383abb6..8fdfc3b7e1d9 100644 --- a/packages/docusaurus-theme-live-codeblock/package.json +++ b/packages/docusaurus-theme-live-codeblock/package.json @@ -13,8 +13,7 @@ "clsx": "^1.1.1", "parse-numeric-range": "^0.0.2", "prism-react-renderer": "^1.1.0", - "react-live": "^2.2.1", - "yup": "^0.29.1" + "react-live": "^2.2.1" }, "peerDependencies": { "@docusaurus/core": "^2.0.0", diff --git a/packages/docusaurus-types/src/index.d.ts b/packages/docusaurus-types/src/index.d.ts index 140f0f0438e4..8f017ac76440 100644 --- a/packages/docusaurus-types/src/index.d.ts +++ b/packages/docusaurus-types/src/index.d.ts @@ -211,7 +211,7 @@ export interface ValidationResult { } export type Validate = ( - validationSchrema: ValidationSchema, + validationSchema: ValidationSchema, options: Partial, ) => ValidationResult; diff --git a/yarn.lock b/yarn.lock index 41d7179c0e3b..f52a3befcf5f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1172,13 +1172,6 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.9.6": - version "7.9.6" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.6.tgz#a9102eb5cadedf3f31d08a9ecf294af7827ea29f" - integrity sha512-64AF1xY3OAkFHqOb9s4jpgk1Mm5vDZ4L3acHvAml+53nO1XbXLuDodsVpO4OIUsmemlUHMxNdYMNJmsvOwLrvQ== - dependencies: - regenerator-runtime "^0.13.4" - "@babel/template@^7.7.4", "@babel/template@^7.8.3", "@babel/template@^7.8.6": version "7.8.6" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b" @@ -1392,17 +1385,7 @@ resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.0.4.tgz#e80ad4e8e8d2adc6c77d985f698447e8628b6010" integrity sha512-EwaJS7RjoXUZ2cXXKZZxZqieGtc7RbvQhUy8FwDoMQtxWVi14tFjeFCYPZAM1mBCpOpiBpyaZbb9NeHc7eGKgw== -"@hapi/joi@^15.1.0": - version "15.1.1" - resolved "https://registry.yarnpkg.com/@hapi/joi/-/joi-15.1.1.tgz#c675b8a71296f02833f8d6d243b34c57b8ce19d7" - integrity sha512-entf8ZMOK8sc+8YfeOlM8pCfg3b5+WZIKBfUaaJT8UsjAAPjartzxIYm3TIbjvA4u+u++KbcXD38k682nVHDAQ== - dependencies: - "@hapi/address" "2.x.x" - "@hapi/bourne" "1.x.x" - "@hapi/hoek" "8.x.x" - "@hapi/topo" "3.x.x" - -"@hapi/joi@^17.1.1": +"@hapi/joi@17.1.1", "@hapi/joi@^17.1.1": version "17.1.1" resolved "https://registry.yarnpkg.com/@hapi/joi/-/joi-17.1.1.tgz#9cc8d7e2c2213d1e46708c6260184b447c661350" integrity sha512-p4DKeZAoeZW4g3u7ZeRo+vCDuSDgSvtsB/NpfjXEHTUjSeINAi/RrVOWiVQ1isaoLzMvFEhe8n5065mQq1AdQg== @@ -1413,6 +1396,16 @@ "@hapi/pinpoint" "^2.0.0" "@hapi/topo" "^5.0.0" +"@hapi/joi@^15.1.0": + version "15.1.1" + resolved "https://registry.yarnpkg.com/@hapi/joi/-/joi-15.1.1.tgz#c675b8a71296f02833f8d6d243b34c57b8ce19d7" + integrity sha512-entf8ZMOK8sc+8YfeOlM8pCfg3b5+WZIKBfUaaJT8UsjAAPjartzxIYm3TIbjvA4u+u++KbcXD38k682nVHDAQ== + dependencies: + "@hapi/address" "2.x.x" + "@hapi/bourne" "1.x.x" + "@hapi/hoek" "8.x.x" + "@hapi/topo" "3.x.x" + "@hapi/pinpoint@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@hapi/pinpoint/-/pinpoint-2.0.0.tgz#805b40d4dbec04fc116a73089494e00f073de8df" @@ -3272,11 +3265,6 @@ dependencies: "@types/yargs-parser" "*" -"@types/yup@^0.29.0": - version "0.29.0" - resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.29.0.tgz#0918ec503dfacb19d0b3cca0195b9f3441f46685" - integrity sha512-E9RTXPD4x44qBOvY6TjUqdkR9FNV9cACWlnAsooUInDqtLZz9M9oYXKn/w1GHNxRvyYyHuG6Bfjbg3QlK+SgXw== - "@typescript-eslint/eslint-plugin@^3.3.0": version "3.3.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.3.0.tgz#89518e5c5209a349bde161c3489b0ec187ae5d37" @@ -8176,11 +8164,6 @@ flush-write-stream@^1.0.0: inherits "^2.0.3" readable-stream "^2.3.6" -fn-name@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/fn-name/-/fn-name-3.0.0.tgz#0596707f635929634d791f452309ab41558e3c5c" - integrity sha512-eNMNr5exLoavuAMhIUVsOKF79SWd/zG104ef6sxBTSw+cZc6BXdQXDvYcGvp0VbxVVSp1XDUNoz7mg1xMtSznA== - follow-redirects@^1.0.0: version "1.9.0" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.9.0.tgz#8d5bcdc65b7108fe1508649c79c12d732dcedb4f" @@ -11332,11 +11315,6 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" -lodash-es@^4.17.11: - version "4.17.15" - resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78" - integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ== - lodash._reinterpolate@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" @@ -14633,11 +14611,6 @@ prop-types@^15.0.0, prop-types@^15.5.0, prop-types@^15.5.4, prop-types@^15.5.8, object-assign "^4.1.1" react-is "^16.8.1" -property-expr@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.2.tgz#fff2a43919135553a3bc2fdd94bdb841965b2330" - integrity sha512-bc/5ggaYZxNkFKj374aLbEDqVADdYaLcFo8XBkishUWbaAdjlphaBFns9TvRA2pUseVL/wMFmui9X3IdNDU37g== - property-information@^5.0.0, property-information@^5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/property-information/-/property-information-5.3.0.tgz#bc87ac82dc4e72a31bb62040544b1bf9653da039" @@ -17310,11 +17283,6 @@ symbol-tree@^3.2.2: resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== -synchronous-promise@^2.0.10: - version "2.0.13" - resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.13.tgz#9d8c165ddee69c5a6542862b405bc50095926702" - integrity sha512-R9N6uDkVsghHePKh1TEqbnLddO2IY25OcsksyFp/qBe7XYd0PVbKEWxhcdMhpLzE1I6skj5l4aEZ3CRxcbArlA== - table@^5.2.3, table@^5.4.6: version "5.4.6" resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" @@ -17694,11 +17662,6 @@ toml@^2.3.2: resolved "https://registry.yarnpkg.com/toml/-/toml-2.3.6.tgz#25b0866483a9722474895559088b436fd11f861b" integrity sha512-gVweAectJU3ebq//Ferr2JUY4WKSDe5N+z0FvjDncLGyHmIDoxgY/2Ie4qfEIDm4IS7OA6Rmdm7pdEEdMcV/xQ== -toposort@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330" - integrity sha1-riF2gXXRVZ1IvvNUILL0li8JwzA= - touch@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" @@ -19132,32 +19095,6 @@ yauzl@^2.4.2: buffer-crc32 "~0.2.3" fd-slicer "~1.1.0" -yup@^0.29.0: - version "0.29.0" - resolved "https://registry.yarnpkg.com/yup/-/yup-0.29.0.tgz#c0670897b2ebcea42ebde12b3567f55ea3a7acaf" - integrity sha512-rXPkxhMIVPsQ6jZXPRcO+nc+AIT+BBo3012pmiEos2RSrPxAq1LyspZyK7l14ahcXuiKQnEHI0H5bptI47v5Tw== - dependencies: - "@babel/runtime" "^7.9.6" - fn-name "~3.0.0" - lodash "^4.17.15" - lodash-es "^4.17.11" - property-expr "^2.0.2" - synchronous-promise "^2.0.10" - toposort "^2.0.2" - -yup@^0.29.1: - version "0.29.1" - resolved "https://registry.yarnpkg.com/yup/-/yup-0.29.1.tgz#35d25aab470a0c3950f66040ba0ff4b1b6efe0d9" - integrity sha512-U7mPIbgfQWI6M3hZCJdGFrr+U0laG28FxMAKIgNvgl7OtyYuUoc4uy9qCWYHZjh49b8T7Ug8NNDdiMIEytcXrQ== - dependencies: - "@babel/runtime" "^7.9.6" - fn-name "~3.0.0" - lodash "^4.17.15" - lodash-es "^4.17.11" - property-expr "^2.0.2" - synchronous-promise "^2.0.10" - toposort "^2.0.2" - zepto@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/zepto/-/zepto-1.2.0.tgz#e127bd9e66fd846be5eab48c1394882f7c0e4f98"