feat(v2): add option validation for remaining official plugins (#2970)

* feat(v2): add option validation lifecycle method

* chore(v2): add dependencies

* chore(v2): add yup dependency

* feat(v2): add option validation for plugin-content-docs

* chore(v2): add facebook copyright

* refactor(v2): remove unused variable

* chore(v2): add dependencies

* chore(v2): add copyright

* fix(v2): use strict for option validation

* feat(v2): add option validation for plugin-content-pages

* feat(v2): add schema for plugin-google-analytics and plugin-google-gtag

* feat(v2): add option validation for plugin-sitemap

* chore(v2): add dependency for yup

* fix(v2): remove strict to allow normalization

* refactor(v2): refactor validate method

* feat(v2): modify existing tests

* feat(v2): add tests for plugin normalization

* style(v2): use a more descriptive filename for schema

* feat(v2): add normalization tests

* feat(v2): add more tests for option validation

* refactor(v2): remove unused code

* refactor(v2): remove unused code

* refactor(v2): refactor methods and types

* feat(v2): replace Yup with Joi

* fix(v2): fix plugin-content-docs schema

* feat(v2): modify tests for plugin-content-docs

* fix(v2): fix a typo

* refactor(v2): improve tests and refactor code

* feat(v2): support both commonjs and ES modules

* refactor(v2): refactor validateOption method

* style(v2): fix eslint errors and typo in types

* chore(v2): remove unused yup dependency

* style(v2): standardize naming across official plugins

* chore(v2): update test snapshots

* chore(v2): remove obsolete snapshots

* chore(v2): fix a typo and check test

* feat(v2): add validation for new field

* feat(v2): add test for new field
This commit is contained in:
Teik Jun 2020-06-26 21:14:59 +08:00 committed by GitHub
parent 3213955e72
commit 0f59cd1599
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 444 additions and 169 deletions

View file

@ -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"
}
}

View file

@ -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);

View file

@ -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']},
});
});

View file

@ -21,7 +21,7 @@ import {
BlogPaginated,
BlogPost,
} from './types';
import {PluginOptionSchema} from './validation';
import {PluginOptionSchema} from './pluginOptionSchema';
import {
LoadContext,
PluginContentLoadedActions,

View file

@ -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),
});

View file

@ -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",

View file

@ -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);

View file

@ -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"`,
);
});
});

View file

@ -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,

View file

@ -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<PluginOptions>,
): Plugin<LoadedContent | null> {
const options: PluginOptions = {...DEFAULT_OPTIONS, ...opts};
options: PluginOptions,
): Plugin<LoadedContent | null, typeof PluginOptionSchema> {
const homePageDocsRoutePath =
options.routeBasePath === '' ? '/' : options.routeBasePath;
@ -551,3 +540,14 @@ Available document ids=
},
};
}
export function validateOptions({
validate,
options,
}: OptionValidationContext<PluginOptions, ValidationError>): ValidationResult<
PluginOptions,
ValidationError
> {
const validatedOptions = validate(PluginOptionSchema, options);
return validatedOptions;
}

View file

@ -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,
),
});

View file

@ -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",

View file

@ -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([

View file

@ -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"`);
});
});

View file

@ -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<PluginOptions>,
): Plugin<LoadedContent | null> {
const options = {...DEFAULT_OPTIONS, ...opts};
options: PluginOptions,
): Plugin<LoadedContent | null, typeof PluginOptionSchema> {
const contentPath = path.resolve(context.siteDir, options.path);
return {
@ -81,3 +81,14 @@ export default function pluginContentPages(
},
};
}
export function validateOptions({
validate,
options,
}: OptionValidationContext<PluginOptions, ValidationError>): ValidationResult<
PluginOptions,
ValidationError
> {
const validatedOptions = validate(PluginOptionSchema, options);
return validatedOptions;
}

View file

@ -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),
});

View file

@ -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"

View file

@ -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]"`,
);
});
});

View file

@ -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<PluginOptions> = {
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<PluginOptions>,
options: PluginOptions,
): Plugin<void> {
const options = {...DEFAULT_OPTIONS, ...opts};
return {
name: 'docusaurus-plugin-sitemap',
@ -44,3 +44,14 @@ export default function pluginSitemap(
},
};
}
export function validateOptions({
validate,
options,
}: OptionValidationContext<PluginOptions, ValidationError>): ValidationResult<
PluginOptions,
ValidationError
> {
const validatedOptions = validate(PluginOptionSchema, options);
return validatedOptions;
}

View file

@ -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<PluginOptions> = {
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),
});

View file

@ -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",

View file

@ -211,7 +211,7 @@ export interface ValidationResult<T, E extends Error = Error> {
}
export type Validate<T, E extends Error = Error> = (
validationSchrema: ValidationSchema<T>,
validationSchema: ValidationSchema<T>,
options: Partial<T>,
) => ValidationResult<T, E>;

View file

@ -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"