Web: Added products/apps dynamic loading

This commit is contained in:
Alexey Safronov 2021-03-12 13:52:30 +03:00
parent eaa46e6c0f
commit e8e78604f1
6 changed files with 256 additions and 136 deletions

View File

@ -56,69 +56,84 @@ class ModuleStore {
iconName = null,
iconUrl = null
) => {
switch (item.id) {
case "6743007c-6f95-4d20-8c88-a8601ce5e76d":
item.iconName = "CrmIcon";
item.iconUrl = "/static/images/crm.react.svg";
item.imageUrl = "/images/crm.svg";
item.helpUrl = "https://helpcenter.onlyoffice.com/userguides/crm.aspx";
break;
case "1e044602-43b5-4d79-82f3-fd6208a11960":
item.iconName = "ProjectsIcon";
item.iconUrl = "/static/images/projects.react.svg";
item.imageUrl = "/images/projects.svg";
item.helpUrl =
"https://helpcenter.onlyoffice.com/userguides/projects.aspx";
break;
case "2A923037-8B2D-487b-9A22-5AC0918ACF3F":
item.iconName = "MailIcon";
item.iconUrl = "/static/images/mail.react.svg";
item.imageUrl = "/images/mail.svg";
break;
case "32D24CB5-7ECE-4606-9C94-19216BA42086":
item.iconName = "CalendarCheckedIcon";
item.iconUrl = "/static/images/calendar.checked.react.svg";
item.imageUrl = "/images/calendar.svg";
break;
case "BF88953E-3C43-4850-A3FB-B1E43AD53A3E":
item.iconName = "ChatIcon";
item.iconUrl = "/static/images/chat.react.svg";
item.imageUrl = "/images/talk.svg";
item.isolateMode = true;
break;
default:
break;
}
const id =
item.id && typeof item.id === "string" ? item.id.toLowerCase() : null;
const actions = noAction
? null
: {
onClick: (e) => {
if (e) {
window.open(item.link, "_self");
e.preventDefault();
}
},
onBadgeClick: (e) => console.log(iconName + " Badge Clicked", e),
};
const description = noAction ? { description: item.description } : null;
return {
id: item.id,
const result = {
id,
appName: "none",
title: item.title,
link: item.link,
originUrl: item.originUrl,
helpUrl: item.helpUrl,
notifications: 0,
iconName: item.iconName || iconName || "/static/images/people.react.svg", //TODO: Change to URL
iconUrl: item.iconUrl || iconUrl,
imageUrl: item.imageUrl,
notifications: 0,
isolateMode: item.isolateMode,
isPrimary: item.isPrimary,
...description,
...actions,
};
switch (id) {
case "6743007c-6f95-4d20-8c88-a8601ce5e76d":
result.appName = "crm";
result.iconName = "CrmIcon";
result.iconUrl = "/static/images/crm.react.svg";
result.imageUrl = "/images/crm.svg";
result.helpUrl =
"https://helpcenter.onlyoffice.com/userguides/crm.aspx";
break;
case "1e044602-43b5-4d79-82f3-fd6208a11960":
result.appName = "projects";
result.iconName = "ProjectsIcon";
result.iconUrl = "/static/images/projects.react.svg";
result.imageUrl = "/images/projects.svg";
result.helpUrl =
"https://helpcenter.onlyoffice.com/userguides/projects.aspx";
break;
case "2a923037-8b2d-487b-9a22-5ac0918acf3f":
result.appName = "mail";
result.iconName = "MailIcon";
result.iconUrl = "/static/images/mail.react.svg";
result.imageUrl = "/images/mail.svg";
break;
case "32d24cb5-7ece-4606-9c94-19216ba42086":
result.appName = "calendar";
result.iconName = "CalendarCheckedIcon";
result.iconUrl = "/static/images/calendar.checked.react.svg";
result.imageUrl = "/images/calendar.svg";
break;
case "bf88953e-3c43-4850-a3fb-b1e43ad53a3e":
result.appName = "chat";
result.iconName = "ChatIcon";
result.iconUrl = "/static/images/chat.react.svg";
result.imageUrl = "/images/talk.svg";
result.isolateMode = true;
break;
case "e67be73d-f9ae-4ce1-8fec-1880cb518cb4":
result.appName = "files";
break;
case "f4d98afd-d336-4332-8778-3c6945c81ea0":
result.appName = "people";
break;
default:
result.appName = "none";
break;
}
if (!noAction) {
result.onClick = (e) => {
if (e) {
window.open(item.link, "_self");
e.preventDefault();
}
};
result.onBadgeClick = (e) => console.log(iconName + " Badge Clicked", e);
} else {
result.description = item.description;
}
return result;
};
get totalNotificationsCount() {

View File

@ -1,5 +1,4 @@
import React, { useEffect } from "react";
import styled from "styled-components";
import { Router, Switch } from "react-router-dom";
import { inject, observer } from "mobx-react";
import NavMenu from "./components/NavMenu";
@ -12,9 +11,6 @@ import Layout from "./components/Layout";
import ScrollToTop from "./components/Layout/ScrollToTop";
import history from "@appserver/common/history";
import toastr from "studio/toastr";
import Loader from "@appserver/components/loader";
import Grid from "@appserver/components/grid";
import PageLayout from "@appserver/common/components/PageLayout";
import { updateTempContent } from "@appserver/common/utils";
import { Provider as MobxProvider } from "mobx-react";
import ThemeProvider from "@appserver/components/theme-provider";
@ -23,35 +19,27 @@ import store from "studio/store";
import config from "../package.json";
import "./custom.scss";
import "./i18n";
import AppLoader from "./components/AppLoader";
import System from "./components/System";
const Payments = React.lazy(() => import("./components/pages/Payments"));
const Error404 = React.lazy(() => import("studio/Error404"));
const Error401 = React.lazy(() => import("studio/Error401"));
const Home = React.lazy(() => import("./components/pages/Home"));
const Login = React.lazy(() => import("login/app"));
const People = React.lazy(() => import("people/app"));
const Files = React.lazy(() => import("files/app"));
const About = React.lazy(() => import("./components/pages/About"));
const Settings = React.lazy(() => import("./components/pages/Settings"));
const ComingSoon = React.lazy(() => import("./components/pages/ComingSoon"));
const LoadingShell = () => (
<PageLayout>
<PageLayout.SectionBody>
<Loader className="pageLoader" type="rombs" size="40px" />
</PageLayout.SectionBody>
</PageLayout>
);
const SettingsRoute = (props) => (
<React.Suspense fallback={<LoadingShell />}>
<React.Suspense fallback={<AppLoader />}>
<ErrorBoundary>
<Settings {...props} />
</ErrorBoundary>
</React.Suspense>
);
const PaymentsRoute = (props) => (
<React.Suspense fallback={<LoadingShell />}>
<React.Suspense fallback={<AppLoader />}>
<ErrorBoundary>
<Payments {...props} />
</ErrorBoundary>
@ -59,7 +47,7 @@ const PaymentsRoute = (props) => (
);
const Error404Route = (props) => (
<React.Suspense fallback={<LoadingShell />}>
<React.Suspense fallback={<AppLoader />}>
<ErrorBoundary>
<Error404 {...props} />
</ErrorBoundary>
@ -67,14 +55,14 @@ const Error404Route = (props) => (
);
const Error401Route = (props) => (
<React.Suspense fallback={<LoadingShell />}>
<React.Suspense fallback={<AppLoader />}>
<ErrorBoundary>
<Error401 {...props} />
</ErrorBoundary>
</React.Suspense>
);
const HomeRoute = (props) => (
<React.Suspense fallback={<LoadingShell />}>
<React.Suspense fallback={<AppLoader />}>
<ErrorBoundary>
<Home {...props} />
</ErrorBoundary>
@ -82,35 +70,15 @@ const HomeRoute = (props) => (
);
const LoginRoute = (props) => (
<React.Suspense fallback={<LoadingShell />}>
<React.Suspense fallback={<AppLoader />}>
<ErrorBoundary>
<Login {...props} />
</ErrorBoundary>
</React.Suspense>
);
const PeopleRoute = (props) => {
return (
<React.Suspense fallback={<LoadingShell />}>
<ErrorBoundary>
<People {...props} />
</ErrorBoundary>
</React.Suspense>
);
};
const FilesRoute = (props) => {
return (
<React.Suspense fallback={<LoadingShell />}>
<ErrorBoundary>
<Files {...props} />
</ErrorBoundary>
</React.Suspense>
);
};
const AboutRoute = (props) => (
<React.Suspense fallback={<LoadingShell />}>
<React.Suspense fallback={<AppLoader />}>
<ErrorBoundary>
<About {...props} />
</ErrorBoundary>
@ -118,12 +86,28 @@ const AboutRoute = (props) => (
);
const ComingSoonRoute = (props) => (
<React.Suspense fallback={<LoadingShell />}>
<React.Suspense fallback={<AppLoader />}>
<ErrorBoundary>
<ComingSoon {...props} />
</ErrorBoundary>
</React.Suspense>
);
const DynamicAppRoute = ({ link, appName, ...rest }) => {
const system = {
url: `${window.location.origin}${link}remoteEntry.js`,
scope: appName,
module: "./app",
};
return (
<React.Suspense fallback={<AppLoader />}>
<ErrorBoundary>
<System system={system} {...rest} />
</ErrorBoundary>
</React.Suspense>
);
};
const Shell = ({ items = [], page = "home", ...rest }) => {
// useEffect(() => {
// //utils.removeTempContent();
@ -159,7 +143,7 @@ const Shell = ({ items = [], page = "home", ...rest }) => {
// .catch((err) => toastr.error(err.message));
// }, []);
const { isLoaded, loadBaseInfo, isThirdPartyResponse } = rest;
const { isLoaded, loadBaseInfo, isThirdPartyResponse, modules } = rest;
useEffect(() => {
try {
@ -167,7 +151,7 @@ const Shell = ({ items = [], page = "home", ...rest }) => {
} catch (err) {
toastr.error(err);
}
}, [loadBaseInfo]);
}, []);
useEffect(() => {
if (isLoaded) updateTempContent();
@ -181,6 +165,16 @@ const Shell = ({ items = [], page = "home", ...rest }) => {
const pathname = window.location.pathname.toLowerCase();
const isEditor = pathname.indexOf("doceditor") !== -1;
const dynamicRoutes = modules.map((m) => (
<PrivateRoute
key={m.id}
path={m.link}
component={DynamicAppRoute}
link={m.link}
appName={m.appName}
/>
));
return (
<Layout>
<Router history={history}>
@ -194,14 +188,6 @@ const Shell = ({ items = [], page = "home", ...rest }) => {
path={["/", "/error=:error"]}
component={HomeRoute}
/>
<PrivateRoute
path={["/products/people", "/products/people/filter"]}
component={PeopleRoute}
/>
<PrivateRoute
path={["/products/files", "/products/files/filter"]}
component={FilesRoute}
/>
<PrivateRoute path={["/about"]} component={AboutRoute} />
<PublicRoute
exact
@ -230,6 +216,7 @@ const Shell = ({ items = [], page = "home", ...rest }) => {
path="/settings"
component={SettingsRoute}
/>
{dynamicRoutes}
<PrivateRoute path="/error401" component={Error401Route} />
<PrivateRoute component={Error404Route} />
</Switch>
@ -242,7 +229,6 @@ const Shell = ({ items = [], page = "home", ...rest }) => {
const ShellWrapper = inject(({ auth }) => {
const { init, isLoaded } = auth;
const pathname = window.location.pathname.toLowerCase();
const isThirdPartyResponse = pathname.indexOf("thirdparty") !== -1;
@ -253,6 +239,7 @@ const ShellWrapper = inject(({ auth }) => {
},
isThirdPartyResponse,
isLoaded,
modules: auth.moduleStore.modules,
};
})(observer(Shell));

View File

@ -0,0 +1,13 @@
import React from "react";
import PageLayout from "@appserver/common/components/PageLayout";
import Loader from "@appserver/components/loader";
const AppLoader = () => (
<PageLayout>
<PageLayout.SectionBody>
<Loader className="pageLoader" type="rombs" size="40px" />
</PageLayout.SectionBody>
</PageLayout>
);
export default AppLoader;

View File

@ -0,0 +1,107 @@
import React from "react";
import AppLoader from "../AppLoader";
import ErrorBoundary from "@appserver/common/components/ErrorBoundary";
import Error520 from "studio/Error520";
import Error404 from "studio/Error404";
function loadComponent(scope, module) {
return async () => {
// Initializes the share scope. This fills it with known provided modules from this build and all remotes
await __webpack_init_sharing__("default");
const container = window[scope]; // or get the container somewhere else
// Initialize the container, it may provide shared modules
await container.init(__webpack_share_scopes__.default);
const factory = await window[scope].get(module);
const Module = factory();
return Module;
};
}
const useDynamicScript = (args) => {
const [ready, setReady] = React.useState(false);
const [failed, setFailed] = React.useState(false);
React.useEffect(() => {
if (!args.url) {
return;
}
const exists = document.getElementById(args.id);
if (exists) {
setReady(true);
setFailed(false);
return;
}
const element = document.createElement("script");
element.id = args.id;
element.src = args.url;
element.type = "text/javascript";
element.async = true;
setReady(false);
setFailed(false);
element.onload = () => {
console.log(`Dynamic Script Loaded: ${args.url}`);
setReady(true);
};
element.onerror = () => {
console.error(`Dynamic Script Error: ${args.url}`);
setReady(false);
setFailed(true);
};
document.head.appendChild(element);
//TODO: Uncomment if you need to remove loaded remoteEntry
// return () => {
// console.log(`Dynamic Script Removed: ${args.url}`);
// document.head.removeChild(element);
// };
}, [args.url]);
return {
ready,
failed,
};
};
const System = (props) => {
const { ready, failed } = useDynamicScript({
url: props.system && props.system.url,
id: props.system && props.system.scope,
});
if (!props.system) {
console.log(`Not system specified`);
return <Error404 />;
}
if (!ready) {
console.log(`Loading dynamic script: ${props.system.url}`);
return <AppLoader />;
}
if (failed) {
console.log(`Failed to load dynamic script: ${props.system.url}`);
return <Error520 />;
}
const Component = React.lazy(
loadComponent(props.system.scope, props.system.module)
);
return (
<React.Suspense fallback={<AppLoader />}>
<ErrorBoundary>
<Component />
</ErrorBoundary>
</React.Suspense>
);
};
export default System;

View File

@ -126,8 +126,6 @@ const config = {
filename: "remoteEntry.js",
remotes: {
studio: `studio@${homepage}/remoteEntry.js`,
people: `people@${homepage}/products/people/remoteEntry.js`,
files: `files@${homepage}/products/files/remoteEntry.js`,
login: `login@${homepage}/login/remoteEntry.js`,
},
exposes: {

View File

@ -5778,9 +5778,9 @@ can-use-dom@^0.1.0:
integrity sha1-IsxKNKCrxDlQ9CxkEQJKP2NmtFo=
caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001125, caniuse-lite@^1.0.30001181:
version "1.0.30001198"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001198.tgz#ed2d9b5f060322ba2efa42afdc56dee3255473f4"
integrity sha512-r5GGgESqOPZzwvdLVER374FpQu2WluCF1Z2DSiFJ89KSmGjT0LVKjgv4NcAqHmGWF9ihNpqRI9KXO9Ex4sKsgA==
version "1.0.30001199"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001199.tgz#062afccaad21023e2e647d767bac4274b8b8fd7f"
integrity sha512-ifbK2eChUCFUwGhlEzIoVwzFt1+iriSjyKKFYNfv6hN34483wyWpLLavYQXhnR036LhkdUYaSDpHg1El++VgHQ==
capture-exit@^2.0.0:
version "2.0.0"
@ -6049,9 +6049,9 @@ cli-width@^3.0.0:
integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==
clipboard@^2.0.0:
version "2.0.7"
resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.7.tgz#da927f817b1859426df39212ac81feb07dbaeadc"
integrity sha512-8M8WEZcIvs0hgOma+wAPkrUxpv0PMY1L6VsAJh/2DOKARIMpyWe6ZLcEoe1qktl6/ced5ceYHs+oGedSbgZ3sg==
version "2.0.8"
resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.8.tgz#ffc6c103dd2967a83005f3f61976aa4655a4cdba"
integrity sha512-Y6WO0unAIQp5bLmk1zdThRhgJt/x3ks6f30s3oE3H1mgIEU33XyQjEf8gsf6DxC7NPX8Y1SsNWjUjL/ywLnnbQ==
dependencies:
good-listener "^1.2.2"
select "^1.1.2"
@ -7318,9 +7318,9 @@ domutils@^1.5.1, domutils@^1.7.0:
domelementtype "1"
domutils@^2.4.2, domutils@^2.4.3, domutils@^2.4.4:
version "2.4.4"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.4.4.tgz#282739c4b150d022d34699797369aad8d19bbbd3"
integrity sha512-jBC0vOsECI4OMdD0GC9mGn7NXPLb+Qt6KW1YDQzeQYRUFKmNG8lh7mO5HiELfr+lLQE7loDVI4QcAxV80HS+RA==
version "2.5.0"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.5.0.tgz#42f49cffdabb92ad243278b331fd761c1c2d3039"
integrity sha512-Ho16rzNMOFk2fPwChGh3D2D9OEHAfG19HgmRR2l+WLSsIstNsAYBzePH412bL0y5T44ejABIVfTHQ8nqi/tBCg==
dependencies:
dom-serializer "^1.0.1"
domelementtype "^2.0.1"
@ -7428,9 +7428,9 @@ ejs@^3.1.2:
jake "^10.6.1"
electron-to-chromium@^1.3.564, electron-to-chromium@^1.3.649:
version "1.3.684"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.684.tgz#053fbb0a4b2d5c076dfa6e1d8ecd06a3075a558a"
integrity sha512-GV/vz2EmmtRSvfGSQ5A0Lucic//IRSDijgL15IgzbBEEnp4rfbxeUSZSlBfmsj7BQvE4sBdgfsvPzLCnp6L21w==
version "1.3.687"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.687.tgz#c336184b7ab70427ffe2ee79eaeaedbc1ad8c374"
integrity sha512-IpzksdQNl3wdgkzf7dnA7/v10w0Utf1dF2L+B4+gKrloBrxCut+au+kky3PYvle3RMdSxZP+UiCZtLbcYRxSNQ==
element-resize-detector@^1.2.1:
version "1.2.2"
@ -12219,9 +12219,9 @@ node-modules-regexp@^1.0.0:
integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=
node-notifier@^5.4.2:
version "5.4.3"
resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.4.3.tgz#cb72daf94c93904098e28b9c590fd866e464bd50"
integrity sha512-M4UBGcs4jeOK9CjTsYwkvH6/MzuUmGCyTW+kCY7uO+1ZVr0+FHGdPdIf5CCLqAaxnRrWidyoQlNkMIIVwbKB8Q==
version "5.4.5"
resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.4.5.tgz#0cbc1a2b0f658493b4025775a13ad938e96091ef"
integrity sha512-tVbHs7DyTLtzOiN78izLA85zRqB9NvEXkAf014Vx3jtSvn/xBl6bR8ZYifj+dFcFrKI21huSQgJZ6ZtL3B4HfQ==
dependencies:
growly "^1.3.0"
is-wsl "^1.1.0"
@ -13758,11 +13758,11 @@ react-dev-utils@^11.0.3:
text-table "0.2.0"
react-device-detect@^1.14.0, react-device-detect@^1.15.0:
version "1.15.0"
resolved "https://registry.yarnpkg.com/react-device-detect/-/react-device-detect-1.15.0.tgz#5321f94ae3c4d51ef399b0502a6c739e32d0f315"
integrity sha512-ywjtWW04U7vaJK87IAFHhKozZhTPeDVWsfYx5CxQSQCjU5+fnMMxWZt9HnVWaNTqBEn6g8wCNWyqav7sXJrURg==
version "1.17.0"
resolved "https://registry.yarnpkg.com/react-device-detect/-/react-device-detect-1.17.0.tgz#a00b4fd6880cebfab3fd8a42a79dc0290cdddca9"
integrity sha512-bBblIStwpHmoS281JFIVqeimcN3LhpoP5YKDWzxQdBIUP8S2xPvHDgizLDhUq2ScguLfVPmwfF5y268EEQR60w==
dependencies:
ua-parser-js "^0.7.23"
ua-parser-js "^0.7.24"
react-docgen-typescript-plugin@^0.6.2:
version "0.6.3"
@ -14832,9 +14832,9 @@ rollup-pluginutils@^2.8.2:
estree-walker "^0.6.1"
rollup@^2.25.0:
version "2.41.0"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.41.0.tgz#b2a398bbabbf227738dedaef099e494aed468982"
integrity sha512-Gk76XHTggulWPH95q8V62bw6uqDH6UGvbD6LOa3QUyhuMF3eOuaeDHR7SLm1T9faitkpNrqzUAVYx47klcMnlA==
version "2.41.1"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.41.1.tgz#c7c7ada42b13be505facd516f13fb697c24c1116"
integrity sha512-nepLFAW5W71/MWpS2Yr7r31eS7HRfYg2RXnxb6ehqN9zY42yACxKtEfb4xq8SmNfUohAzGMcyl6jkwdLOAiUbg==
optionalDependencies:
fsevents "~2.3.1"
@ -16533,7 +16533,7 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
ua-parser-js@^0.7.23:
ua-parser-js@^0.7.24:
version "0.7.24"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.24.tgz#8d3ecea46ed4f1f1d63ec25f17d8568105dc027c"
integrity sha512-yo+miGzQx5gakzVK3QFfN0/L9uVhosXBBO7qmnk7c2iw1IhL212wfA3zbnI54B0obGwC/5NWub/iT9sReMx+Fw==
@ -16547,9 +16547,9 @@ uglify-js@3.4.x:
source-map "~0.6.1"
uglify-js@^3.1.4:
version "3.13.0"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.13.0.tgz#66ed69f7241f33f13531d3d51d5bcebf00df7f69"
integrity sha512-TWYSWa9T2pPN4DIJYbU9oAjQx+5qdV5RUDxwARg8fmJZrD/V27Zj0JngW5xg1DFz42G0uDYl2XhzF6alSzD62w==
version "3.13.1"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.13.1.tgz#2749d4b8b5b7d67460b4a418023ff73c3fefa60a"
integrity sha512-EWhx3fHy3M9JbaeTnO+rEqzCe1wtyQClv6q3YWq0voOj4E+bMZBErVS1GAHPDiRGONYq34M1/d8KuQMgvi6Gjw==
uid-number@0.0.6:
version "0.0.6"
@ -17731,9 +17731,9 @@ yargs-parser@^15.0.1:
decamelize "^1.2.0"
yargs-parser@^20.2.3:
version "20.2.6"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.6.tgz#69f920addf61aafc0b8b89002f5d66e28f2d8b20"
integrity sha512-AP1+fQIWSM/sMiET8fyayjx/J+JmTPt2Mr0FkrgqB4todtfa53sOsrSAcIrJRD5XS20bKUwaDIuMkWKCEiQLKA==
version "20.2.7"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.7.tgz#61df85c113edfb5a7a4e36eb8aa60ef423cbc90a"
integrity sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==
yargs@^13.0.0, yargs@^13.3.0, yargs@^13.3.2:
version "13.3.2"