feat(v2): debug pages + debug layout + ability to debug content (#3229)

* improve debug plugin:
- add multiple debug pages + debug layout
- ability to debug plugin contentLoaded data

* add missing dependency

* fix broken test

* improve content rendering a bit

* create basic DebugJsonView

* fix ReactJson SSR issues
This commit is contained in:
Sébastien Lorber 2020-08-07 11:47:43 +02:00 committed by GitHub
parent be210a1bc4
commit fe281a8ebe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 511 additions and 110 deletions

View file

@ -12,6 +12,7 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "^2.0.0-alpha.61",
"@docusaurus/types": "^2.0.0-alpha.61",
"@docusaurus/utils": "^2.0.0-alpha.61",
"@hapi/joi": "^17.1.1",

View file

@ -39,6 +39,7 @@ describe('normalizePluginOptions', () => {
redirects: [{from: '/x', to: '/y'}],
}),
).toEqual({
id: 'default',
fromExtensions: ['exe', 'zip'],
toExtensions: ['html'],
createRedirects,

View file

@ -8,8 +8,10 @@
import {PluginOptions, RedirectOption, UserPluginOptions} from './types';
import * as Joi from '@hapi/joi';
import {PathnameValidator} from './redirectValidation';
import {DEFAULT_PLUGIN_ID} from '@docusaurus/core/lib/constants';
export const DefaultPluginOptions: PluginOptions = {
id: DEFAULT_PLUGIN_ID, // TODO temporary
fromExtensions: [],
toExtensions: [],
redirects: [],
@ -26,6 +28,7 @@ const RedirectPluginOptionValidation = Joi.object<RedirectOption>({
const isString = Joi.string().required().not(null);
const UserOptionsSchema = Joi.object<UserPluginOptions>({
id: Joi.string().optional(), // TODO remove once validation migrated to new system
fromExtensions: Joi.array().items(isString),
toExtensions: Joi.array().items(isString),
redirects: Joi.array().items(RedirectPluginOptionValidation),

View file

@ -8,6 +8,7 @@
import {Props} from '@docusaurus/types';
export type PluginOptions = {
id: string;
fromExtensions: string[];
toExtensions: string[];
redirects: RedirectOption[];

View file

@ -28,7 +28,6 @@ import {
import {PluginOptionSchema} from './pluginOptionSchema';
import {
LoadContext,
PluginContentLoadedActions,
ConfigureWebpackUtils,
Props,
Plugin,
@ -195,13 +194,7 @@ export default function pluginContentBlog(
};
},
async contentLoaded({
content: blogContents,
actions,
}: {
content: BlogContent;
actions: PluginContentLoadedActions;
}) {
async contentLoaded({content: blogContents, actions}) {
if (!blogContents) {
return;
}

View file

@ -13,7 +13,8 @@
"license": "MIT",
"dependencies": {
"@docusaurus/types": "^2.0.0-alpha.61",
"@docusaurus/utils": "^2.0.0-alpha.61"
"@docusaurus/utils": "^2.0.0-alpha.61",
"react-json-view": "^1.19.1"
},
"peerDependencies": {
"@docusaurus/core": "^2.0.0",

View file

@ -6,13 +6,20 @@
*/
import {LoadContext, Plugin} from '@docusaurus/types';
import {normalizeUrl} from '@docusaurus/utils';
import {docuHash, normalizeUrl} from '@docusaurus/utils';
import path from 'path';
export default function pluginContentPages({
siteConfig: {baseUrl},
generatedFilesDir,
}: LoadContext): Plugin<void> {
const pluginDataDirRoot = path.join(
generatedFilesDir,
'docusaurus-plugin-debug',
);
const aliasedSource = (source: string) =>
`~debug/${path.relative(pluginDataDirRoot, source)}`;
return {
name: 'docusaurus-plugin-debug',
@ -20,12 +27,63 @@ export default function pluginContentPages({
return path.resolve(__dirname, '../src/theme');
},
contentLoaded({actions: {addRoute}}) {
async contentLoaded({actions: {createData, addRoute}, allContent}) {
const allContentPath = await createData(
// Note that this created data path must be in sync with
// metadataPath provided to mdx-loader.
`${docuHash('docusaurus-debug-allContent')}.json`,
JSON.stringify(allContent, null, 2),
);
// Home is config (duplicate for now)
addRoute({
path: normalizeUrl([baseUrl, '__docusaurus/debug']),
component: '@theme/Debug',
component: '@theme/DebugConfig',
exact: true,
});
addRoute({
path: normalizeUrl([baseUrl, '__docusaurus/debug/config']),
component: '@theme/DebugConfig',
exact: true,
});
addRoute({
path: normalizeUrl([baseUrl, '__docusaurus/debug/metadata']),
component: '@theme/DebugMetadata',
exact: true,
});
addRoute({
path: normalizeUrl([baseUrl, '__docusaurus/debug/registry']),
component: '@theme/DebugRegistry',
exact: true,
});
addRoute({
path: normalizeUrl([baseUrl, '__docusaurus/debug/routes']),
component: '@theme/DebugRoutes',
exact: true,
});
addRoute({
path: normalizeUrl([baseUrl, '__docusaurus/debug/content']),
component: '@theme/DebugContent',
exact: true,
modules: {
allContent: aliasedSource(allContentPath),
},
});
},
configureWebpack() {
return {
resolve: {
alias: {
'~debug': pluginDataDirRoot,
},
},
};
},
};
}

View file

@ -1,70 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import React from 'react';
import Layout from '@theme/Layout';
import registry from '@generated/registry';
import routes from '@generated/routes';
import styles from './styles.module.css';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
function Debug() {
const {siteMetadata} = useDocusaurusContext();
return (
<Layout permalink="__docusaurus/debug" title="Debug">
<main className={styles.Container}>
<section className={styles.Section}>
<h2>Site Metadata</h2>
<div>Docusaurus Version: {siteMetadata.docusaurusVersion}</div>
<div>
Site Version: {siteMetadata.siteVersion || 'No version specified'}
</div>
<h3>Plugins and themes:</h3>
<ul>
{Object.entries(siteMetadata.pluginVersions).map(
([name, versionInformation]) => (
<li key={name}>
<div>Name: {name}</div>
<div>Type: {versionInformation.type}</div>
{versionInformation.version && (
<div>Version: {versionInformation.version}</div>
)}
</li>
),
)}
</ul>
</section>
<section className={styles.Section}>
<h2>Registry</h2>
<ul>
{Object.values(registry).map(([, aliasedPath, resolved]) => (
<li key={aliasedPath}>
<div>Aliased Path: {aliasedPath}</div>
<div>Resolved Path: {resolved}</div>
</li>
))}
</ul>
</section>
<section className={styles.Section}>
<h2>Routes</h2>
<ul>
{routes.map(({path, exact}) => (
<li key={path}>
<div>Route: {path}</div>
<div>Is exact: {String(Boolean(exact))}</div>
</li>
))}
</ul>
</section>
</main>
</Layout>
);
}
export default Debug;

View file

@ -0,0 +1,23 @@
/**
* 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 React from 'react';
import DebugLayout from '../DebugLayout';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
function DebugMetadata() {
const {siteConfig} = useDocusaurusContext();
return (
<DebugLayout>
<h2>Site config</h2>
<div>{JSON.stringify(siteConfig, null, 2)}</div>
</DebugLayout>
);
}
export default DebugMetadata;

View file

@ -5,13 +5,3 @@
* LICENSE file in the root directory of this source tree.
*/
.Container {
display: flex;
flex-wrap: wrap;
justify-content: center;
margin: 1em;
}
.Section {
width: 500px;
}

View file

@ -0,0 +1,84 @@
/**
* 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 React, {useState} from 'react';
import DebugLayout from '../DebugLayout';
import DebugJsonView from '../DebugJsonView';
const PluginInstanceContent = ({pluginId, pluginInstanceContent}) => (
<section style={{marginBottom: 30}}>
<h4>{`>> ${pluginId}`}</h4>
<div
style={{
marginTop: 10,
padding: 10,
border: 'thin cyan solid',
borderRadius: 5,
backgroundColor: 'lightgrey',
}}>
<DebugJsonView src={pluginInstanceContent} />
</div>
</section>
);
const PluginContent = ({pluginName, pluginContent}) => {
const [visible, setVisible] = useState(true);
return (
<section style={{marginBottom: 60}}>
<h3 onClick={() => setVisible((v) => !v)} style={{cursor: 'pointer'}}>
{pluginName}
</h3>
{visible && (
<div>
{Object.entries(pluginContent)
// filter plugin instances with no content
.filter(
([_pluginId, pluginInstanceContent]) => !!pluginInstanceContent,
)
.map(([pluginId, pluginInstanceContent]) => {
return (
<PluginInstanceContent
key={pluginId}
pluginId={pluginId}
pluginInstanceContent={pluginInstanceContent}
/>
);
})}
</div>
)}
</section>
);
};
function DebugContent({allContent}) {
return (
<DebugLayout>
<h2>Plugin content</h2>
<div>
{Object.entries(allContent)
// filter plugins with no content
.filter(([_pluginName, pluginContent]) =>
Object.values(pluginContent).some(
(instanceContent) => !!instanceContent,
),
)
.map(([pluginName, pluginContent]) => {
return (
<PluginContent
key={pluginName}
pluginName={pluginName}
pluginContent={pluginContent}
/>
);
})}
</div>
</DebugLayout>
);
}
export default DebugContent;

View file

@ -0,0 +1,7 @@
/**
* 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.
*/

View file

@ -0,0 +1,43 @@
/**
* 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 React from 'react';
import BrowserOnly from '@docusaurus/BrowserOnly';
// avoids "react-json-view" to display "root"
const RootName = false;
// Seems ReactJson does not work with SSR
// https://github.com/mac-s-g/react-json-view/issues/121
const BrowserOnlyReactJson = (props) => {
return (
<BrowserOnly>
{() => {
const ReactJson = require('react-json-view').default;
return <ReactJson {...props} />;
}}
</BrowserOnly>
);
};
function DebugJsonView({src}) {
return (
<BrowserOnlyReactJson
src={src}
name={RootName}
shouldCollapse={(field) => {
// By default, we collapse the json for performance reasons
// See https://github.com/mac-s-g/react-json-view/issues/235
// only the "root" is not collapsed
return field.name !== RootName;
}}
/>
);
}
export default DebugJsonView;

View file

@ -0,0 +1,7 @@
/**
* 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.
*/

View file

@ -0,0 +1,39 @@
/**
* 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 React from 'react';
import Link from '@docusaurus/Link';
// import styles from './styles.module.css';
const DebugNavLink = ({to, children}) => (
<Link
style={{margin: 10}}
className="button button--primary"
isNavLink
activeClassName="button--active"
to={to}
exact>
{children}
</Link>
);
function DebugLayout({children}) {
return (
<div>
<nav style={{width: '100%', padding: 10, border: 'solid'}}>
<DebugNavLink to="/__docusaurus/debug">Config</DebugNavLink>
<DebugNavLink to="/__docusaurus/debug/metadata">Metadata</DebugNavLink>
<DebugNavLink to="/__docusaurus/debug/registry">Registry</DebugNavLink>
<DebugNavLink to="/__docusaurus/debug/routes">Routes</DebugNavLink>
<DebugNavLink to="/__docusaurus/debug/content">Content</DebugNavLink>
</nav>
<main style={{padding: 20}}>{children}</main>
</div>
);
}
export default DebugLayout;

View file

@ -0,0 +1,6 @@
/**
* 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.
*/

View file

@ -0,0 +1,40 @@
/**
* 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 React from 'react';
import DebugLayout from '../DebugLayout';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
function DebugMetadata() {
const {siteMetadata} = useDocusaurusContext();
return (
<DebugLayout>
<h2>Site Metadata</h2>
<div>Docusaurus Version: {siteMetadata.docusaurusVersion}</div>
<div>
Site Version: {siteMetadata.siteVersion || 'No version specified'}
</div>
<h3>Plugins and themes:</h3>
<ul>
{Object.entries(siteMetadata.pluginVersions).map(
([name, versionInformation]) => (
<li key={name}>
<div>Name: {name}</div>
<div>Type: {versionInformation.type}</div>
{versionInformation.version && (
<div>Version: {versionInformation.version}</div>
)}
</li>
),
)}
</ul>
</DebugLayout>
);
}
export default DebugMetadata;

View file

@ -0,0 +1,7 @@
/**
* 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.
*/

View file

@ -0,0 +1,30 @@
/**
* 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 React from 'react';
import DebugLayout from '../DebugLayout';
import registry from '@generated/registry';
function DebugRegistry() {
return (
<DebugLayout>
{' '}
<h2>Registry</h2>
<ul>
{Object.values(registry).map(([, aliasedPath, resolved]) => (
<li key={aliasedPath}>
<div>Aliased Path: {aliasedPath}</div>
<div>Resolved Path: {resolved}</div>
</li>
))}
</ul>
</DebugLayout>
);
}
export default DebugRegistry;

View file

@ -0,0 +1,7 @@
/**
* 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.
*/

View file

@ -0,0 +1,29 @@
/**
* 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 React from 'react';
import DebugLayout from '../DebugLayout';
import routes from '@generated/routes';
function DebugRoutes() {
return (
<DebugLayout>
<h2>Routes</h2>
<ul>
{routes.map(({path, exact}) => (
<li key={path}>
<div>Route: {path}</div>
<div>Is exact: {String(Boolean(exact))}</div>
</li>
))}
</ul>
</DebugLayout>
);
}
export default DebugRoutes;

View file

@ -0,0 +1,7 @@
/**
* 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.
*/

View file

@ -11,6 +11,9 @@ module.exports = function preset(context, opts = {}) {
const {algolia, googleAnalytics, gtag} = themeConfig;
const isProd = process.env.NODE_ENV === 'production';
const debug =
typeof opts.debug !== 'undefined' ? Boolean(opts.debug) : !isProd;
return {
themes: [
[require.resolve('@docusaurus/theme-classic'), opts.theme],
@ -24,7 +27,7 @@ module.exports = function preset(context, opts = {}) {
isProd &&
googleAnalytics &&
require.resolve('@docusaurus/plugin-google-analytics'),
!isProd && require.resolve('@docusaurus/plugin-debug'),
debug && require.resolve('@docusaurus/plugin-debug'),
isProd && gtag && require.resolve('@docusaurus/plugin-google-gtag'),
isProd && [require.resolve('@docusaurus/plugin-sitemap'), opts.sitemap],
],

View file

@ -126,8 +126,15 @@ export interface PluginContentLoadedActions {
setGlobalData<T = unknown>(data: T): void;
}
export type AllContent = Record<
string, // plugin name
Record<
string, // plugin id
unknown // plugin data
>
>;
export interface Plugin<T, U = unknown> {
id?: string;
name: string;
loadContent?(): Promise<T>;
validateOptions?(): ValidationResult<U>;
@ -136,7 +143,8 @@ export interface Plugin<T, U = unknown> {
content,
actions,
}: {
content: T;
content: T; // the content loaded by this plugin instance
allContent: AllContent; // content loaded by ALL the plugins
actions: PluginContentLoadedActions;
}): void;
routesLoaded?(routes: RouteConfig[]): void; // TODO remove soon, deprecated (alpha-60)

View file

@ -9,6 +9,7 @@ import {generate} from '@docusaurus/utils';
import fs from 'fs-extra';
import path from 'path';
import {
AllContent,
LoadContext,
PluginConfig,
PluginContentLoadedActions,
@ -17,6 +18,7 @@ import {
import initPlugins, {InitPlugin} from './init';
import chalk from 'chalk';
import {DEFAULT_PLUGIN_ID} from '../../constants';
import {chain} from 'lodash';
export function sortConfig(routeConfigs: RouteConfig[]): void {
// Sort the route config. This ensures that route with nested
@ -68,23 +70,31 @@ export async function loadPlugins({
// Currently plugins run lifecycle methods in parallel and are not order-dependent.
// We could change this in future if there are plugins which need to
// run in certain order or depend on others for data.
const pluginsLoadedContent = await Promise.all(
type ContentLoadedPlugin = {plugin: InitPlugin; content: unknown};
const contentLoadedPlugins: ContentLoadedPlugin[] = await Promise.all(
plugins.map(async (plugin) => {
if (!plugin.loadContent) {
return null;
}
return plugin.loadContent();
const content = plugin.loadContent ? await plugin.loadContent() : null;
return {plugin, content};
}),
);
const allContent: AllContent = chain(contentLoadedPlugins)
.groupBy((item) => item.plugin.name)
.mapValues((nameItems) => {
return chain(nameItems)
.groupBy((item) => item.plugin.options.id ?? DEFAULT_PLUGIN_ID)
.mapValues((idItems) => idItems[0].content)
.value();
})
.value();
// 3. Plugin Lifecycle - contentLoaded.
const pluginsRouteConfigs: RouteConfig[] = [];
const globalData = {};
await Promise.all(
plugins.map(async (plugin, index) => {
contentLoadedPlugins.map(async ({plugin, content}) => {
if (!plugin.contentLoaded) {
return;
}
@ -100,11 +110,11 @@ export async function loadPlugins({
const createData: PluginContentLoadedActions['createData'] = async (
name,
content,
data,
) => {
const modulePath = path.join(dataDir, name);
await fs.ensureDir(path.dirname(modulePath));
await generate(dataDir, name, content);
await generate(dataDir, name, data);
return modulePath;
};
@ -125,8 +135,9 @@ export async function loadPlugins({
};
await plugin.contentLoaded({
content: pluginsLoadedContent[index],
content,
actions,
allContent,
});
}),
);

View file

@ -15,7 +15,7 @@ import {
PluginConfig,
DocusaurusPluginVersionInformation,
} from '@docusaurus/types';
import {CONFIG_FILE_NAME} from '../../constants';
import {CONFIG_FILE_NAME, DEFAULT_PLUGIN_ID} from '../../constants';
import {getPluginVersion} from '../versions';
import {ensureUniquePluginInstanceIds} from './pluginIds';
import {
@ -79,6 +79,13 @@ export default function initPlugins({
options: pluginOptions,
});
pluginOptions = normalizedOptions;
} else {
// Important to ensure all plugins have an id
// as we don't go through the Joi schema that adds it
pluginOptions = {
...pluginOptions,
id: pluginOptions.id ?? DEFAULT_PLUGIN_ID,
};
}
// support both commonjs and ES modules

View file

@ -17,10 +17,12 @@
/* Additional Checks */
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
/* Disabled on purpose (handled by ESLint, should not block compilation) */
"noUnusedParameters": false,
/* Module Resolution Options */
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,

View file

@ -156,6 +156,7 @@ module.exports = {
[
'@docusaurus/preset-classic',
{
debug: true, // force debug plugin usage
docs: {
// routeBasePath: '/',
path: 'docs',

View file

@ -5440,6 +5440,11 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
base16@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/base16/-/base16-1.0.0.tgz#e297f60d7ec1014a7a971a39ebc8a98c0b681e70"
integrity sha1-4pf2DX7BAUp6lxo568ipjAtoHnA=
base64-js@^1.0.2:
version "1.3.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
@ -9642,7 +9647,14 @@ fb-watchman@^2.0.0:
dependencies:
bser "2.1.1"
fbjs@^0.8.0:
fbemitter@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/fbemitter/-/fbemitter-2.1.1.tgz#523e14fdaf5248805bb02f62efc33be703f51865"
integrity sha1-Uj4U/a9SSIBbsC9i78M75wP1GGU=
dependencies:
fbjs "^0.8.4"
fbjs@^0.8.0, fbjs@^0.8.4:
version "0.8.17"
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd"
integrity sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=
@ -9956,6 +9968,14 @@ flush-write-stream@^2.0.0:
inherits "^2.0.3"
readable-stream "^3.1.1"
flux@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/flux/-/flux-3.1.3.tgz#d23bed515a79a22d933ab53ab4ada19d05b2f08a"
integrity sha1-0jvtUVp5oi2TOrU6tK2hnQWy8Io=
dependencies:
fbemitter "^2.0.0"
fbjs "^0.8.0"
fn.name@1.x.x:
version "1.1.0"
resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc"
@ -13541,6 +13561,11 @@ lodash.clonedeep@^4.5.0:
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
lodash.curry@^4.0.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.curry/-/lodash.curry-4.1.1.tgz#248e36072ede906501d75966200a86dab8b23170"
integrity sha1-JI42By7ekGUB11lmIAqG2riyMXA=
lodash.debounce@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
@ -13581,6 +13606,11 @@ lodash.flattendeep@^4.4.0:
resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2"
integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=
lodash.flow@^3.3.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/lodash.flow/-/lodash.flow-3.5.0.tgz#87bf40292b8cf83e4e8ce1a3ae4209e20071675a"
integrity sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o=
lodash.foreach@^4.3.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz#1a6a35eace401280c7f06dddec35165ab27e3e53"
@ -17227,7 +17257,7 @@ prop-types-exact@^1.2.0:
object.assign "^4.1.0"
reflect.ownkeys "^0.2.0"
prop-types@^15.0.0, prop-types@^15.5.0, prop-types@^15.5.8, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
prop-types@^15.0.0, prop-types@^15.5.0, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@ -17355,6 +17385,11 @@ pupa@^2.0.1:
dependencies:
escape-goat "^2.0.0"
pure-color@^1.2.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/pure-color/-/pure-color-1.3.0.tgz#1fe064fb0ac851f0de61320a8bf796836422f33e"
integrity sha1-H+Bk+wrIUfDeYTIKi/eWg2Qi8z4=
q@^1.1.2, q@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
@ -17524,6 +17559,16 @@ react-attr-converter@^0.3.1:
resolved "https://registry.yarnpkg.com/react-attr-converter/-/react-attr-converter-0.3.1.tgz#4a2abf6d907b7ddae4d862dfec80e489ce41ad6e"
integrity sha512-dSxo2Mn6Zx4HajeCeQNLefwEO4kNtV/0E682R1+ZTyFRPqxDa5zYb5qM/ocqw9Bxr/kFQO0IUiqdV7wdHw+Cdg==
react-base16-styling@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/react-base16-styling/-/react-base16-styling-0.6.0.tgz#ef2156d66cf4139695c8a167886cb69ea660792c"
integrity sha1-7yFW1mz0E5aVyKFniGy2nqZgeSw=
dependencies:
base16 "^1.0.0"
lodash.curry "^4.0.1"
lodash.flow "^3.3.0"
pure-color "^1.2.0"
react-dev-utils@^10.2.1:
version "10.2.1"
resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-10.2.1.tgz#f6de325ae25fa4d546d09df4bb1befdc6dd19c19"
@ -17620,6 +17665,16 @@ react-is@^16.12.0, react-is@^16.6.0, react-is@^16.6.3, react-is@^16.7.0, react-i
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-json-view@^1.19.1:
version "1.19.1"
resolved "https://registry.yarnpkg.com/react-json-view/-/react-json-view-1.19.1.tgz#95d8e59e024f08a25e5dc8f076ae304eed97cf5c"
integrity sha512-u5e0XDLIs9Rj43vWkKvwL8G3JzvXSl6etuS5G42a8klMohZuYFQzSN6ri+/GiBptDqlrXPTdExJVU7x9rrlXhg==
dependencies:
flux "^3.1.3"
react-base16-styling "^0.6.0"
react-lifecycles-compat "^3.0.4"
react-textarea-autosize "^6.1.0"
react-lifecycles-compat@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
@ -17722,6 +17777,13 @@ react-test-renderer@^16.0.0-0:
react-is "^16.8.6"
scheduler "^0.19.1"
react-textarea-autosize@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-6.1.0.tgz#df91387f8a8f22020b77e3833c09829d706a09a5"
integrity sha512-F6bI1dgib6fSvG8so1HuArPUv+iVEfPliuLWusLF+gAKz0FbB4jLrWUrTAeq1afnPT2c9toEZYUdz/y1uKMy4A==
dependencies:
prop-types "^15.6.0"
react-toggle@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/react-toggle/-/react-toggle-4.1.1.tgz#2317f67bf918ea3508a96b09dd383efd9da572af"