mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-10 07:37:19 +02:00
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:
parent
3213955e72
commit
0f59cd1599
24 changed files with 444 additions and 169 deletions
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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']},
|
||||
});
|
||||
});
|
|
@ -21,7 +21,7 @@ import {
|
|||
BlogPaginated,
|
||||
BlogPost,
|
||||
} from './types';
|
||||
import {PluginOptionSchema} from './validation';
|
||||
import {PluginOptionSchema} from './pluginOptionSchema';
|
||||
import {
|
||||
LoadContext,
|
||||
PluginContentLoadedActions,
|
||||
|
|
|
@ -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),
|
||||
});
|
|
@ -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",
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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"`,
|
||||
);
|
||||
});
|
||||
});
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
});
|
|
@ -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",
|
||||
|
|
|
@ -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([
|
||||
|
|
|
@ -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"`);
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
});
|
|
@ -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"
|
||||
|
|
|
@ -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]"`,
|
||||
);
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
|
|
22
packages/docusaurus-plugin-sitemap/src/pluginOptionSchema.ts
Normal file
22
packages/docusaurus-plugin-sitemap/src/pluginOptionSchema.ts
Normal 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),
|
||||
});
|
|
@ -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",
|
||||
|
|
2
packages/docusaurus-types/src/index.d.ts
vendored
2
packages/docusaurus-types/src/index.d.ts
vendored
|
@ -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>;
|
||||
|
||||
|
|
85
yarn.lock
85
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"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue