From 120b9d2e6d6dc8dca8e828419230b2e18cfa1abf Mon Sep 17 00:00:00 2001 From: richburdon Date: Sat, 23 May 2020 16:16:35 -0400 Subject: [PATCH] Error boundaries and laoding status. --- README.md | 19 ++-- package.json | 7 +- packages/console-client/config.json | 5 +- packages/console-client/gql/status.graphql | 5 + packages/console-client/package.json | 24 +++++ .../src/components/ErrorBoundary.js | 53 +++++++++++ .../console-client/src/components/Layout.js | 44 +++++++++ .../src/containers/ConsoleContextProvider.js | 64 +++++++++++++ .../console-client/src/containers/Main.js | 14 ++- .../src/{components => containers}/Status.js | 16 +--- packages/console-client/src/hooks/context.js | 11 +++ packages/console-client/src/hooks/index.js | 6 ++ packages/console-client/src/hooks/status.js | 47 ++++++++++ packages/console-client/src/index.js | 2 +- packages/console-client/version.json | 2 +- packages/console-server/config.json | 4 - packages/console-server/package.json | 23 +++++ packages/console-server/src/gql/api.graphql | 5 + .../console-server/src/gql/status.graphql | 5 - packages/console-server/src/main.js | 11 +-- packages/console-server/src/resolvers.js | 5 +- yarn.lock | 94 ++++++++++++++----- 22 files changed, 401 insertions(+), 65 deletions(-) create mode 100644 packages/console-client/src/components/ErrorBoundary.js create mode 100644 packages/console-client/src/components/Layout.js create mode 100644 packages/console-client/src/containers/ConsoleContextProvider.js rename packages/console-client/src/{components => containers}/Status.js (52%) create mode 100644 packages/console-client/src/hooks/context.js create mode 100644 packages/console-client/src/hooks/index.js create mode 100644 packages/console-client/src/hooks/status.js delete mode 100644 packages/console-server/config.json delete mode 100644 packages/console-server/src/gql/status.graphql diff --git a/README.md b/README.md index 4899d37..03a9839 100644 --- a/README.md +++ b/README.md @@ -4,16 +4,23 @@ Apollo GraphQL client and server using express. ## Tasks -- [ ] Server React app from server. - - https://www.freecodecamp.org/news/how-to-set-up-deploy-your-react-app-from-scratch-using-webpack-and-babel-a669891033d4/ +### POC +- [ ] Refresh button. - [ ] Trigger server-side commands (separate express path?) +- [ ] Test backend IPFS request. +- [ ] Layout/Router (with Material UI). -- [ ] Material UI. -- [ ] Router. +### Next + +- [ ] Lint settings for webstorm (bug?) - [ ] Shared config. -- [ ] IPFS request. -- [ ] Port modules with dummy resolvers. +- [ ] Port dashboard modules with dummy resolvers. +### Done + +- [x] Error boundary. +- [x] Server React app from server. + - https://www.freecodecamp.org/news/how-to-set-up-deploy-your-react-app-from-scratch-using-webpack-and-babel-a669891033d4/ - [x] Monorepo for client/server. - [x] Basic React/Apollo component. diff --git a/package.json b/package.json index fe9edbe..be0ce54 100644 --- a/package.json +++ b/package.json @@ -32,11 +32,15 @@ "devDependencies": { "babel-eslint": "^10.0.3", "eslint": "^6.7.2", - "eslint-config-airbnb": "^18.0.0", "eslint-loader": "^3.0.3", + "eslint-config-semistandard": "^15.0.0", + "eslint-config-standard": "^14.1.1", + "eslint-config-standard-jsx": "^8.1.0", "eslint-plugin-import": "^2.18.2", "eslint-plugin-jsdoc": "^21.0.0", "eslint-plugin-jsx-a11y": "^6.2.3", + "eslint-plugin-monorepo": "^0.2.1", + "eslint-plugin-node": "^11.1.0", "lint-staged": "^9.5.0", "pre-commit": "^1.2.2", "semistandard": "^14.2.0" @@ -45,6 +49,7 @@ "parser": "babel-eslint", "extends": [ "plugin:jest/recommended", + "plugin:monorepo/recommended", "semistandard", "standard-jsx" ], diff --git a/packages/console-client/config.json b/packages/console-client/config.json index 40aaa11..e6f36be 100644 --- a/packages/console-client/config.json +++ b/packages/console-client/config.json @@ -1,5 +1,6 @@ { - "public_url": "/app", + "server": "http://localhost", + "publicUrl": "/app", "port": 4000, - "path": "/graphql" + "path": "/api" } diff --git a/packages/console-client/gql/status.graphql b/packages/console-client/gql/status.graphql index f86f7fb..af4bc4b 100644 --- a/packages/console-client/gql/status.graphql +++ b/packages/console-client/gql/status.graphql @@ -1,5 +1,10 @@ +# +# Copyright 2020 DxOS +# + { status { + timestamp version } } diff --git a/packages/console-client/package.json b/packages/console-client/package.json index 77f1fad..8e0ccb7 100644 --- a/packages/console-client/package.json +++ b/packages/console-client/package.json @@ -32,6 +32,7 @@ "apollo-boost": "^0.4.9", "debug": "^4.1.1", "graphql-tag": "^2.10.3", + "lodash.defaultsdeep": "^4.6.1", "react": "^16.13.1", "react-dom": "^16.13.1", "source-map-support": "^0.5.12" @@ -52,6 +53,7 @@ "dotenv-webpack": "^1.8.0", "eslint-plugin-babel": "^5.3.0", "eslint-plugin-jest": "^23.13.1", + "eslint-plugin-node": "^11.1.0", "eslint-plugin-react": "^7.17.0", "html-webpack-plugin": "^4.3.0", "jest": "^24.8.0", @@ -66,5 +68,27 @@ }, "publishConfig": { "access": "public" + }, + "eslintConfig": { + "parser": "babel-eslint", + "extends": [ + "plugin:jest/recommended", + "semistandard", + "standard-jsx" + ], + "plugins": [ + "babel" + ], + "rules": { + "babel/semi": 1 + } + }, + "semistandard": { + "parser": "babel-eslint", + "env": [ + "jest", + "node", + "browser" + ] } } diff --git a/packages/console-client/src/components/ErrorBoundary.js b/packages/console-client/src/components/ErrorBoundary.js new file mode 100644 index 0000000..f4486df --- /dev/null +++ b/packages/console-client/src/components/ErrorBoundary.js @@ -0,0 +1,53 @@ +// +// Copyright 2020 DxOS +// + +import React, { Component } from 'react'; + +/** + * Root-level error boundary. + * https://reactjs.org/docs/error-boundaries.html + * + * NOTE: Must currently be a Component. + * https://reactjs.org/docs/hooks-faq.html#do-hooks-cover-all-use-cases-for-classes + */ +class ErrorBoundary extends Component { + static getDerivedStateFromError (error) { + return { error }; + } + + state = { + error: null + }; + + componentDidCatch (error, errorInfo) { + const { onError } = this.props; + + // TODO(burdon): Show error indicator. + // TODO(burdon): Logging service; output error file. + onError(error); + } + + render () { + const { children } = this.props; + const { error } = this.state; + + if (error) { + return ( +
{String(error)}
+ ); + } + + return ( +
+ {children} +
+ ); + } +} + +ErrorBoundary.defaultProps = { + onError: console.warn +}; + +export default ErrorBoundary; diff --git a/packages/console-client/src/components/Layout.js b/packages/console-client/src/components/Layout.js new file mode 100644 index 0000000..660306e --- /dev/null +++ b/packages/console-client/src/components/Layout.js @@ -0,0 +1,44 @@ +// +// Copyright 2020 DxOS +// + +import React, { useEffect, useState } from 'react'; + +import { useStatusReducer } from '../hooks'; + +// TODO(burdon): Factor out LoadingIndicator. +const Layout = ({ children }) => { + const [{ loading, error = '' }] = useStatusReducer(); + const [isLoading, setLoading] = useState(loading); + + useEffect(() => { + let t; + if (loading) { + setLoading(loading); + t = setTimeout(() => { + setLoading(false); + }, 1000); + } + + return () => clearTimeout(t); + }, [loading]); + + return ( +
+
+ {children} +
+ +
+ {error && ( + {String(error)} + )} + {isLoading && ( + Loading + )} +
+
+ ); +}; + +export default Layout; diff --git a/packages/console-client/src/containers/ConsoleContextProvider.js b/packages/console-client/src/containers/ConsoleContextProvider.js new file mode 100644 index 0000000..476c1a0 --- /dev/null +++ b/packages/console-client/src/containers/ConsoleContextProvider.js @@ -0,0 +1,64 @@ +// +// Copyright 2020 Wireline, Inc. +// + +import React, { useEffect, useReducer } from 'react'; +import defaultsDeep from 'lodash.defaultsdeep'; + +import ErrorBoundary from '../components/ErrorBoundary'; + +import { statusReducer, SET_STATUS } from '../hooks/status'; + +import { ConsoleContext } from '../hooks'; + +const defaultState = {}; + +/** + * Actions reducer. + * https://reactjs.org/docs/hooks-reference.html#usereducer + * @param {Object} state + * @param {string} action + */ +const appReducer = (state, action) => ({ + // TODO(burdon): Key shouldn't be same as action type. + [SET_STATUS]: statusReducer(state[SET_STATUS], action) +}); + +/** + * Creates the Console framework context, which provides the global UX state. + * Wraps children with a React ErrorBoundary component, which catches runtime errors and enables reset. + * + * @param {function} children + * @param {Object} [initialState] + * @param {function} [errorHandler] + * @returns {function} + */ +const ConsoleContextProvider = ({ children, initialState = {}, errorHandler }) => { + const [state, dispatch] = useReducer(appReducer, defaultsDeep({}, initialState, defaultState)); + + const { errors: { exceptions = [] } = {} } = state[SET_STATUS] || {}; + + // Bind the error handler. + if (errorHandler) { + useEffect(() => { + errorHandler.on('error', error => { + dispatch({ + type: SET_STATUS, + payload: { + exceptions: [error, ...exceptions] + } + }); + }); + }, []); + } + + return ( + + + {children} + + + ); +}; + +export default ConsoleContextProvider; diff --git a/packages/console-client/src/containers/Main.js b/packages/console-client/src/containers/Main.js index d9b1e44..fb07ac1 100644 --- a/packages/console-client/src/containers/Main.js +++ b/packages/console-client/src/containers/Main.js @@ -6,24 +6,30 @@ import { ApolloProvider } from '@apollo/react-hooks'; import ApolloClient from 'apollo-boost'; import React from 'react'; -import Status from '../components/Status'; +import Status from './Status'; import config from '../../config.json'; +import Layout from '../components/Layout'; +import ConsoleContextProvider from './ConsoleContextProvider'; -const { port, path } = config; +const { server, port = 80, path } = config; // TODO(burdon): Error handling for server errors. // TODO(burdon): Authentication: // https://www.apollographql.com/docs/react/networking/authentication/ const client = new ApolloClient({ - uri: `http://localhost:${port}${path}` + uri: `${server}:${port}${path}` }); const Main = () => { return ( - + + + + + ); }; diff --git a/packages/console-client/src/components/Status.js b/packages/console-client/src/containers/Status.js similarity index 52% rename from packages/console-client/src/components/Status.js rename to packages/console-client/src/containers/Status.js index d698559..588b17e 100644 --- a/packages/console-client/src/components/Status.js +++ b/packages/console-client/src/containers/Status.js @@ -2,24 +2,18 @@ // Copyright 2020 DxOS // -import debug from 'debug'; import React from 'react'; import { useQuery } from '@apollo/react-hooks'; +import { useQueryStatusReducer } from '../hooks'; + import QUERY_STATUS from '../../gql/status.graphql'; -const log = debug('dxos:console:client:app'); - const Status = () => { - const { loading, error, data } = useQuery(QUERY_STATUS); - if (loading) { - return
Loading...
; + const data = useQueryStatusReducer(useQuery(QUERY_STATUS, { pollInterval: 5000 })); + if (!data) { + return null; } - if (error) { - return
Error: ${error}
; - } - - log(JSON.stringify(data)); return (
diff --git a/packages/console-client/src/hooks/context.js b/packages/console-client/src/hooks/context.js
new file mode 100644
index 0000000..1d2ae4d
--- /dev/null
+++ b/packages/console-client/src/hooks/context.js
@@ -0,0 +1,11 @@
+//
+// Copyright 2020 Wireline, Inc.
+//
+
+import { createContext } from 'react';
+
+/**
+ * https://reactjs.org/docs/context.html#reactcreatecontext
+ * @type {React.Context}
+ */
+export const ConsoleContext = createContext({});
diff --git a/packages/console-client/src/hooks/index.js b/packages/console-client/src/hooks/index.js
new file mode 100644
index 0000000..8a1512b
--- /dev/null
+++ b/packages/console-client/src/hooks/index.js
@@ -0,0 +1,6 @@
+//
+// Copyright 2020 Wireline, Inc.
+//
+
+export * from './context';
+export * from './status';
diff --git a/packages/console-client/src/hooks/status.js b/packages/console-client/src/hooks/status.js
new file mode 100644
index 0000000..ea2d3e8
--- /dev/null
+++ b/packages/console-client/src/hooks/status.js
@@ -0,0 +1,47 @@
+//
+// Copyright 2019 Wireline, Inc.
+//
+
+import { useContext } from 'react';
+
+import { ConsoleContext } from './context';
+
+export const SET_STATUS = 'errors';
+
+export const useStatusReducer = () => {
+  const { state, dispatch } = useContext(ConsoleContext);
+  return [
+    state[SET_STATUS] || {},
+    value => dispatch({ type: SET_STATUS, payload: value || { exceptions: [] } })
+  ];
+};
+
+/**
+ * Handle Apollo queries.
+ */
+export const useQueryStatusReducer = ({ loading, error, data }) => {
+  const [, setStatus] = useStatusReducer();
+
+  if (loading) {
+    setTimeout(() => setStatus({ loading }));
+  }
+
+  if (error) {
+    setTimeout(() => setStatus({ error }));
+  }
+
+  return data;
+};
+
+export const statusReducer = (state, action) => {
+  switch (action.type) {
+    case SET_STATUS:
+      return {
+        ...state,
+        ...action.payload
+      };
+
+    default:
+      return state;
+  }
+};
diff --git a/packages/console-client/src/index.js b/packages/console-client/src/index.js
index 642ff54..3659b8c 100644
--- a/packages/console-client/src/index.js
+++ b/packages/console-client/src/index.js
@@ -2,4 +2,4 @@
 // Copyright 2020 DxOS
 //
 
-export Main from './main';
+export * from './hooks';
diff --git a/packages/console-client/version.json b/packages/console-client/version.json
index fa8ff3e..88dfb45 100644
--- a/packages/console-client/version.json
+++ b/packages/console-client/version.json
@@ -1,7 +1,7 @@
 {
   "build": {
     "name":      "@dxos/console-client",
-    "buildDate": "2020-05-23T18:35:48.873Z",
+    "buildDate": "2020-05-23T20:00:53.818Z",
     "version":   "1.0.0-beta.0"
   }
 }
diff --git a/packages/console-server/config.json b/packages/console-server/config.json
deleted file mode 100644
index 9e73ad6..0000000
--- a/packages/console-server/config.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
-  "port": 4000,
-  "path": "/graphql"
-}
diff --git a/packages/console-server/package.json b/packages/console-server/package.json
index 0641be8..6ca92dc 100644
--- a/packages/console-server/package.json
+++ b/packages/console-server/package.json
@@ -48,5 +48,28 @@
   },
   "publishConfig": {
     "access": "public"
+  },
+  "eslintConfig": {
+    "parser": "babel-eslint",
+    "extends": [
+      "plugin:jest/recommended",
+      "semistandard",
+      "standard-jsx"
+    ],
+    "plugins": [
+      "babel",
+      "node"
+    ],
+    "rules": {
+      "babel/semi": 1
+    }
+  },
+  "semistandard": {
+    "parser": "babel-eslint",
+    "env": [
+      "jest",
+      "node",
+      "browser"
+    ]
   }
 }
diff --git a/packages/console-server/src/gql/api.graphql b/packages/console-server/src/gql/api.graphql
index a1673c1..fafbee9 100644
--- a/packages/console-server/src/gql/api.graphql
+++ b/packages/console-server/src/gql/api.graphql
@@ -1,4 +1,9 @@
+#
+# Copyright 2020 DxOS
+#
+
 type Status {
+  timestamp: String
   version: String
 }
 
diff --git a/packages/console-server/src/gql/status.graphql b/packages/console-server/src/gql/status.graphql
deleted file mode 100644
index f86f7fb..0000000
--- a/packages/console-server/src/gql/status.graphql
+++ /dev/null
@@ -1,5 +0,0 @@
-{
-  status {
-    version
-  }
-}
diff --git a/packages/console-server/src/main.js b/packages/console-server/src/main.js
index 913b94c..f848888 100644
--- a/packages/console-server/src/main.js
+++ b/packages/console-server/src/main.js
@@ -9,12 +9,10 @@ import { ApolloServer, gql } from 'apollo-server-express';
 import { print } from 'graphql/language';
 
 import QUERY_STATUS from '@dxos/console-client/gql/status.graphql';
-import clientConfig from '@dxos/console-client/config.json';
+import config from '@dxos/console-client/config.json';
 
 import { resolvers } from './resolvers';
 
-import config from '../config.json';
-
 import SCHEMA from './gql/api.graphql';
 
 const log = debug('dxos:console:server');
@@ -44,11 +42,12 @@ const app = express();
 // React app
 //
 
-const { public_url } = clientConfig;
+const { publicUrl } = config;
 
-app.get(`${public_url}(/:filePath)?`, (req, res) => {
+app.get(`${publicUrl}(/:filePath)?`, (req, res) => {
   const { filePath = 'index.html' } = req.params;
-  const file = path.join(__dirname + '../../../../node_modules/@dxos/console-client/dist/production', filePath);
+  const file = path.join(__dirname, '../../../node_modules/@dxos/console-client/dist/production', filePath);
+  console.log(__dirname, file);
   res.sendFile(file);
 });
 
diff --git a/packages/console-server/src/resolvers.js b/packages/console-server/src/resolvers.js
index c4da881..cbd75cc 100644
--- a/packages/console-server/src/resolvers.js
+++ b/packages/console-server/src/resolvers.js
@@ -2,11 +2,11 @@
 // Copyright 2020 DxOS
 //
 
-import debug from 'debug';
+// import debug from 'debug';
 
 import { version } from '../package.json';
 
-const log = debug('dxos:console:resolver');
+// const log = debug('dxos:console:resolver');
 
 //
 // Resolver
@@ -15,6 +15,7 @@ const log = debug('dxos:console:resolver');
 export const resolvers = {
   Query: {
     status: () => ({
+      timestamp: new Date().toUTCString(),
       version
     })
   }
diff --git a/yarn.lock b/yarn.lock
index 5ec6830..e344ca8 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5818,7 +5818,7 @@ dir-glob@2.0.0:
     arrify "^1.0.1"
     path-type "^3.0.0"
 
-dir-glob@^2.2.2:
+dir-glob@^2.0.0, dir-glob@^2.2.2:
   version "2.2.2"
   resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.2.2.tgz#fa09f0694153c8918b18ba0deafae94769fc50c4"
   integrity sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==
@@ -6250,24 +6250,6 @@ escodegen@^1.11.0, escodegen@^1.9.1:
   optionalDependencies:
     source-map "~0.6.1"
 
-eslint-config-airbnb-base@^14.1.0:
-  version "14.1.0"
-  resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.1.0.tgz#2ba4592dd6843258221d9bff2b6831bd77c874e4"
-  integrity sha512-+XCcfGyCnbzOnktDVhwsCAx+9DmrzEmuwxyHUJpw+kqBVT744OUBrB09khgFKlK1lshVww6qXGsYPZpavoNjJw==
-  dependencies:
-    confusing-browser-globals "^1.0.9"
-    object.assign "^4.1.0"
-    object.entries "^1.1.1"
-
-eslint-config-airbnb@^18.0.0:
-  version "18.1.0"
-  resolved "https://registry.yarnpkg.com/eslint-config-airbnb/-/eslint-config-airbnb-18.1.0.tgz#724d7e93dadd2169492ff5363c5aaa779e01257d"
-  integrity sha512-kZFuQC/MPnH7KJp6v95xsLBf63G/w7YqdPfQ0MUanxQ7zcKUNG8j+sSY860g3NwCBOa62apw16J6pRN+AOgXzw==
-  dependencies:
-    eslint-config-airbnb-base "^14.1.0"
-    object.assign "^4.1.0"
-    object.entries "^1.1.1"
-
 eslint-config-react-app@^5.2.1:
   version "5.2.1"
   resolved "https://registry.yarnpkg.com/eslint-config-react-app/-/eslint-config-react-app-5.2.1.tgz#698bf7aeee27f0cea0139eaef261c7bf7dd623df"
@@ -6275,12 +6257,12 @@ eslint-config-react-app@^5.2.1:
   dependencies:
     confusing-browser-globals "^1.0.9"
 
-eslint-config-semistandard@15.0.0:
+eslint-config-semistandard@15.0.0, eslint-config-semistandard@^15.0.0:
   version "15.0.0"
   resolved "https://registry.yarnpkg.com/eslint-config-semistandard/-/eslint-config-semistandard-15.0.0.tgz#d8eefccfac4ca9cbc508d38de6cb8fd5e7a72fa9"
   integrity sha512-volIMnosUvzyxGkYUA5QvwkahZZLeUx7wcS0+7QumPn+MMEBbV6P7BY1yukamMst0w3Et3QZlCjQEwQ8tQ6nug==
 
-eslint-config-standard-jsx@8.1.0:
+eslint-config-standard-jsx@8.1.0, eslint-config-standard-jsx@^8.1.0:
   version "8.1.0"
   resolved "https://registry.yarnpkg.com/eslint-config-standard-jsx/-/eslint-config-standard-jsx-8.1.0.tgz#314c62a0e6f51f75547f89aade059bec140edfc7"
   integrity sha512-ULVC8qH8qCqbU792ZOO6DaiaZyHNS/5CZt3hKqHkEhVlhPEPN3nfBqqxJCyp59XrjIBZPu1chMYe9T2DXZ7TMw==
@@ -6290,6 +6272,11 @@ eslint-config-standard@14.1.0:
   resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-14.1.0.tgz#b23da2b76fe5a2eba668374f246454e7058f15d4"
   integrity sha512-EF6XkrrGVbvv8hL/kYa/m6vnvmUT+K82pJJc4JJVMM6+Qgqh0pnwprSxdduDLB9p/7bIxD+YV5O0wfb8lmcPbA==
 
+eslint-config-standard@^14.1.1:
+  version "14.1.1"
+  resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-14.1.1.tgz#830a8e44e7aef7de67464979ad06b406026c56ea"
+  integrity sha512-Z9B+VR+JIXRxz21udPTL9HpFMyoMUEeX1G251EQ6e05WD9aPVtVBn09XUmZ259wCMlCDmYDSZG62Hhm+ZTJcUg==
+
 eslint-import-resolver-node@^0.3.2:
   version "0.3.3"
   resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.3.tgz#dbaa52b6b2816b50bc6711af75422de808e98404"
@@ -6320,7 +6307,7 @@ eslint-loader@^3.0.3:
     object-hash "^2.0.3"
     schema-utils "^2.6.5"
 
-eslint-module-utils@^2.4.0, eslint-module-utils@^2.4.1:
+eslint-module-utils@^2.1.1, eslint-module-utils@^2.4.0, eslint-module-utils@^2.4.1:
   version "2.6.0"
   resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz#579ebd094f56af7797d19c9866c9c9486629bfa6"
   integrity sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA==
@@ -6343,6 +6330,14 @@ eslint-plugin-es@^2.0.0:
     eslint-utils "^1.4.2"
     regexpp "^3.0.0"
 
+eslint-plugin-es@^3.0.0:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz#75a7cdfdccddc0589934aeeb384175f221c57893"
+  integrity sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==
+  dependencies:
+    eslint-utils "^2.0.0"
+    regexpp "^3.0.0"
+
 eslint-plugin-flowtype@4.6.0:
   version "4.6.0"
   resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-4.6.0.tgz#82b2bd6f21770e0e5deede0228e456cb35308451"
@@ -6438,6 +6433,31 @@ eslint-plugin-jsx-a11y@6.2.3, eslint-plugin-jsx-a11y@^6.2.3:
     has "^1.0.3"
     jsx-ast-utils "^2.2.1"
 
+eslint-plugin-monorepo@^0.2.1:
+  version "0.2.1"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-monorepo/-/eslint-plugin-monorepo-0.2.1.tgz#96cfc4af241077675f40d7017377897fb8ea537b"
+  integrity sha512-82JaAjuajVAsDT+pMvdt275H6F55H3MEofaMZbJurGqfXpPDT4eayTgYyyjfd1XR8VD1S+ORbuHCULnSqNyD9g==
+  dependencies:
+    eslint-module-utils "^2.1.1"
+    get-monorepo-packages "^1.1.0"
+    globby "^7.1.1"
+    load-json-file "^4.0.0"
+    minimatch "^3.0.4"
+    parse-package-name "^0.1.0"
+    path-is-inside "^1.0.2"
+
+eslint-plugin-node@^11.1.0:
+  version "11.1.0"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz#c95544416ee4ada26740a30474eefc5402dc671d"
+  integrity sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==
+  dependencies:
+    eslint-plugin-es "^3.0.0"
+    eslint-utils "^2.0.0"
+    ignore "^5.1.1"
+    minimatch "^3.0.4"
+    resolve "^1.10.1"
+    semver "^6.1.0"
+
 eslint-plugin-node@~10.0.0:
   version "10.0.0"
   resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-10.0.0.tgz#fd1adbc7a300cf7eb6ac55cf4b0b6fc6e577f5a6"
@@ -7367,6 +7387,14 @@ get-caller-file@^2.0.1:
   resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
   integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
 
+get-monorepo-packages@^1.1.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/get-monorepo-packages/-/get-monorepo-packages-1.2.0.tgz#3eee88d30b11a5f65955dec6ae331958b2a168e4"
+  integrity sha512-aDP6tH+eM3EuVSp3YyCutOcFS4Y9AhRRH9FAd+cjtR/g63Hx+DCXdKoP1ViRPUJz5wm+BOEXB4FhoffGHxJ7jQ==
+  dependencies:
+    globby "^7.1.1"
+    load-json-file "^4.0.0"
+
 get-own-enumerable-property-symbols@^3.0.0:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664"
@@ -7598,6 +7626,18 @@ globby@^6.1.0:
     pify "^2.0.0"
     pinkie-promise "^2.0.0"
 
+globby@^7.1.1:
+  version "7.1.1"
+  resolved "https://registry.yarnpkg.com/globby/-/globby-7.1.1.tgz#fb2ccff9401f8600945dfada97440cca972b8680"
+  integrity sha1-+yzP+UAfhgCUXfral0QMypcrhoA=
+  dependencies:
+    array-union "^1.0.1"
+    dir-glob "^2.0.0"
+    glob "^7.1.2"
+    ignore "^3.3.5"
+    pify "^3.0.0"
+    slash "^1.0.0"
+
 globby@^9.2.0:
   version "9.2.0"
   resolved "https://registry.yarnpkg.com/globby/-/globby-9.2.0.tgz#fd029a706c703d29bdd170f4b6db3a3f7a7cb63d"
@@ -9692,6 +9732,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.defaultsdeep@^4.6.1:
+  version "4.6.1"
+  resolved "https://registry.yarnpkg.com/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz#512e9bd721d272d94e3d3a63653fa17516741ca6"
+  integrity sha512-3j8wdDzYuWO3lM3Reg03MuQR957t287Rpcxp1njpEa8oDrikb+FwGdW3n+FELh/A6qib6yPit0j/pv9G/yeAqA==
+
 lodash.get@^4.4.2:
   version "4.4.2"
   resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
@@ -11138,6 +11183,11 @@ parse-json@^5.0.0:
     json-parse-better-errors "^1.0.1"
     lines-and-columns "^1.1.6"
 
+parse-package-name@^0.1.0:
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/parse-package-name/-/parse-package-name-0.1.0.tgz#3f44dd838feb4c2be4bf318bae4477d7706bade4"
+  integrity sha1-P0Tdg4/rTCvkvzGLrkR313BrreQ=
+
 parse-passwd@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6"