refactor(v4): Base admin migration

pull/25/head
Boaz Poolman 2021-10-14 17:13:12 +02:00
parent 75711f7da4
commit 41a9bfdf6c
15 changed files with 198 additions and 233 deletions

View File

@ -2,7 +2,8 @@ import React, { useState } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
import { Button } from '@buffetjs/core'; import { Button } from '@strapi/parts/Button';
import ConfirmModal from '../ConfirmModal'; import ConfirmModal from '../ConfirmModal';
import { exportAllConfig, importAllConfig } from '../../state/actions/Config'; import { exportAllConfig, importAllConfig } from '../../state/actions/Config';
@ -23,8 +24,8 @@ const ActionButtons = ({ diff }) => {
return ( return (
<ActionButtonsStyling> <ActionButtonsStyling>
<Button disabled={isEmpty(diff.diff)} color="primary" label="Import" onClick={() => openModal('import')} /> <Button disabled={isEmpty(diff.diff)} label="Import" onClick={() => openModal('import')} />
<Button disabled={isEmpty(diff.diff)} color="primary" label="Export" onClick={() => openModal('export')} /> <Button disabled={isEmpty(diff.diff)} label="Export" onClick={() => openModal('export')} />
{!isEmpty(diff.diff) && ( {!isEmpty(diff.diff) && (
<h4 style={{ display: 'inline' }}>{Object.keys(diff.diff).length} {Object.keys(diff.diff).length === 1 ? "config change" : "config changes"}</h4> <h4 style={{ display: 'inline' }}>{Object.keys(diff.diff).length} {Object.keys(diff.diff).length === 1 ? "config change" : "config changes"}</h4>
)} )}
@ -32,11 +33,11 @@ const ActionButtons = ({ diff }) => {
isOpen={modalIsOpen} isOpen={modalIsOpen}
onClose={closeModal} onClose={closeModal}
type={actionType} type={actionType}
onSubmit={() => actionType === 'import' ? dispatch(importAllConfig()) : dispatch(exportAllConfig())} onSubmit={actionType === 'import' ? dispatch(importAllConfig()) : dispatch(exportAllConfig())}
/> />
</ActionButtonsStyling> </ActionButtonsStyling>
); );
} };
const ActionButtonsStyling = styled.div` const ActionButtonsStyling = styled.div`
padding: 10px 0 20px 0; padding: 10px 0 20px 0;

View File

@ -1,48 +1,41 @@
import React from 'react'; import React from 'react';
import ReactDiffViewer, { DiffMethod } from 'react-diff-viewer'; import ReactDiffViewer, { DiffMethod } from 'react-diff-viewer';
import { AttributeIcon } from '@buffetjs/core';
import { import { ModalLayout, ModalFooter, ModalBody, ModalHeader } from '@strapi/parts/ModalLayout';
HeaderModal, import { ButtonText } from '@strapi/parts/Text';
HeaderModalTitle, import { Button } from '@strapi/parts/Button';
Modal,
ModalBody,
ModalFooter,
} from 'strapi-helper-plugin';
const ConfigDiff = ({ isOpen, onClose, onToggle, oldValue, newValue, configName }) => { const ConfigDiff = ({ isOpen, onClose, onToggle, oldValue, newValue, configName }) => {
if (!isOpen) {
return null;
}
return ( return (
<Modal <ModalLayout
isOpen={isOpen} onClose={onClose}
onClosed={onClose} labelledBy="title"
onToggle={onToggle}
> >
<HeaderModal> <ModalHeader>
<section style={{ alignItems: 'center' }}> <ButtonText textColor="neutral800" as="h2" id="title">
<AttributeIcon type='enum' /> Config changes for {configName}
<HeaderModalTitle style={{ marginLeft: 15 }}>Config changes for {configName}</HeaderModalTitle> </ButtonText>
</section> </ModalHeader>
</HeaderModal> <ModalBody>
<ModalBody style={{
paddingTop: '0.5rem',
paddingBottom: '3rem'
}}>
<div className="container-fluid">
<section style={{ marginTop: 20 }}> <section style={{ marginTop: 20 }}>
<ReactDiffViewer <ReactDiffViewer
oldValue={JSON.stringify(oldValue, null, 2)} oldValue={JSON.stringify(oldValue, null, 2)}
newValue={JSON.stringify(newValue, null, 2)} newValue={JSON.stringify(newValue, null, 2)}
splitView={true} splitView
compareMethod={DiffMethod.WORDS} compareMethod={DiffMethod.WORDS}
/> />
</section> </section>
</div>
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
<section style={{ alignItems: 'center' }}> <section style={{ alignItems: 'center' }}>
</section> </section>
</ModalFooter> </ModalFooter>
</Modal> </ModalLayout>
); );
} }

View File

@ -4,24 +4,8 @@ import styled from 'styled-components';
const CustomRow = ({ row }) => { const CustomRow = ({ row }) => {
const { config_name, config_type, state, onClick } = row; const { config_name, config_type, state, onClick } = row;
return (
<tr onClick={() => onClick(config_type, config_name)}>
<td>
<p>{config_name}</p>
</td>
<td>
<p>{config_type}</p>
</td>
<td>
<p style={stateStyle(state)}>{state}</p>
</td>
</tr>
);
};
const stateStyle = (state) => { const stateStyle = (state) => {
let style = { const style = {
display: 'inline-flex', display: 'inline-flex',
padding: '0 10px', padding: '0 10px',
borderRadius: '12px', borderRadius: '12px',
@ -48,5 +32,19 @@ const stateStyle = (state) => {
return style; return style;
}; };
return (
<tr onClick={() => onClick(config_type, config_name)}>
<td>
<p>{config_name}</p>
</td>
<td>
<p>{config_type}</p>
</td>
<td>
<p style={stateStyle(state)}>{state}</p>
</td>
</tr>
);
};
export default CustomRow export default CustomRow;

View File

@ -1,25 +1,17 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Table } from '@buffetjs/core';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
import { NoContent } from '@strapi/helper-plugin';
import AddIcon from '@strapi/icons/AddIcon';
import { VisuallyHidden } from '@strapi/parts/VisuallyHidden';
import { Table, Thead, Tbody, Tr, Th, TFooter } from '@strapi/parts/Table';
import { TableLabel } from '@strapi/parts/Text';
import { Button } from '@strapi/parts/Button';
import ConfigDiff from '../ConfigDiff'; import ConfigDiff from '../ConfigDiff';
import FirstExport from '../FirstExport'; import FirstExport from '../FirstExport';
import ConfigListRow from './ConfigListRow'; import ConfigListRow from './ConfigListRow';
const headers = [
{
name: 'Config name',
value: 'config_name',
},
{
name: 'Config type',
value: 'config_type',
},
{
name: 'State',
value: 'state',
},
];
const ConfigList = ({ diff, isLoading }) => { const ConfigList = ({ diff, isLoading }) => {
const [openModal, setOpenModal] = useState(false); const [openModal, setOpenModal] = useState(false);
const [originalConfig, setOriginalConfig] = useState({}); const [originalConfig, setOriginalConfig] = useState({});
@ -81,7 +73,7 @@ const ConfigList = ({ diff, isLoading }) => {
}; };
if (!isLoading && !isEmpty(diff.message)) { if (!isLoading && !isEmpty(diff.message)) {
return <FirstExport /> return <FirstExport />;
} }
return ( return (
@ -94,15 +86,28 @@ const ConfigList = ({ diff, isLoading }) => {
onToggle={closeModal} onToggle={closeModal}
configName={configName} configName={configName}
/> />
<Table <Table colCount={4} rowCount={rows.length + 1}>
headers={headers} <Thead>
customRow={ConfigListRow} <Tr>
rows={!isLoading ? rows : []} <Th>
isLoading={isLoading} <TableLabel>Config name</TableLabel>
tableEmptyText="No config changes. You are up to date!" </Th>
/> <Th>
<TableLabel>Config type</TableLabel>
</Th>
<Th>
<TableLabel>State</TableLabel>
</Th>
</Tr>
</Thead>
<Tbody>
{rows.map((row) => (
<ConfigListRow key={row.name} {...row} />
))}
</Tbody>
</Table>
</div> </div>
); );
} };
export default ConfigList; export default ConfigList;

View File

@ -1,7 +1,12 @@
import React from 'react'; import React from 'react';
import {
ModalConfirm, import { Dialog, DialogBody, DialogFooter } from '@strapi/parts/Dialog';
} from 'strapi-helper-plugin'; import { Row } from '@strapi/parts/Row';
import { Text } from '@strapi/parts/Text';
import { Stack } from '@strapi/parts/Stack';
import { Button } from '@strapi/parts/Button';
import DeleteIcon from '@strapi/icons/DeleteIcon';
import AlertWarningIcon from '@strapi/icons/AlertWarningIcon';
import getTrad from '../../helpers/getTrad'; import getTrad from '../../helpers/getTrad';
@ -9,26 +14,37 @@ const ConfirmModal = ({ isOpen, onClose, onSubmit, type }) => {
if (!isOpen) return null; if (!isOpen) return null;
return ( return (
<ModalConfirm <Dialog
confirmButtonLabel={{ onClose={onClose}
id: getTrad(`popUpWarning.button.${type}`), title="Confirmation"
}}
isOpen={isOpen} isOpen={isOpen}
toggle={onClose} >
onClosed={onClose} <DialogBody icon={<AlertWarningIcon />}>
onConfirm={() => { <Stack size={2}>
<Row justifyContent="center">
<Text id="confirm-description">{getTrad(`popUpWarning.warning.${type}`)}</Text>
</Row>
</Stack>
</DialogBody>
<DialogFooter
startAction={(
<Button
onClick={() => {
onClose(); onClose();
onSubmit(); onSubmit();
}} }}
type="success" variant="tertiary"
content={{ >
id: getTrad(`popUpWarning.warning.${type}`), Cancel
values: { </Button>
br: () => <br />, )}
}, endAction={(
}} <Button variant="danger-light" startIcon={<DeleteIcon />}>
/> {getTrad(`popUpWarning.button.${type}`)}
</Button>
)} />
</Dialog>
); );
} };
export default ConfirmModal; export default ConfirmModal;

View File

@ -1,24 +0,0 @@
import styled from 'styled-components';
const ContainerFluid = styled.div`
padding: 18px 30px;
> div:first-child {
max-height: 33px;
}
.buttonOutline {
height: 30px;
padding: 0 15px;
border: 1px solid #dfe0e1;
font-weight: 500;
font-size: 13px;
&:before {
margin-right: 10px;
content: '\f08e';
font-family: 'FontAwesome';
font-size: 10px;
}
}
`;
export default ContainerFluid;

View File

@ -1,6 +1,8 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { Button } from '@buffetjs/core';
import { Button } from '@strapi/parts/Button';
import { exportAllConfig } from '../../state/actions/Config'; import { exportAllConfig } from '../../state/actions/Config';
import ConfirmModal from '../ConfirmModal'; import ConfirmModal from '../ConfirmModal';
@ -20,14 +22,18 @@ const FirstExport = () => {
<ConfirmModal <ConfirmModal
isOpen={modalIsOpen} isOpen={modalIsOpen}
onClose={() => setModalIsOpen(false)} onClose={() => setModalIsOpen(false)}
type={'export'} type="export"
onSubmit={() => dispatch(exportAllConfig())} onSubmit={() => dispatch(exportAllConfig())}
/> />
<h3>Looks like this is your first time using config-sync for this project.</h3> <h3>Looks like this is your first time using config-sync for this project.</h3>
<p>Make the initial export!</p> <p>Make the initial export!</p>
<Button color="primary" onClick={() => setModalIsOpen(true)}>Initial export</Button> <Button
onClick={() => setModalIsOpen(true)}
>
Initial export
</Button>
</div> </div>
); );
} };
export default FirstExport; export default FirstExport;

View File

@ -5,21 +5,22 @@
*/ */
import React, { memo } from 'react'; import React, { memo } from 'react';
import { Header } from '@buffetjs/custom'; import { useIntl } from 'react-intl';
import { useGlobalContext } from 'strapi-helper-plugin';
import { HeaderLayout } from '@strapi/parts/Layout';
import { Box } from '@strapi/parts/Box';
const HeaderComponent = () => { const HeaderComponent = () => {
const { formatMessage } = useGlobalContext(); const { formatMessage } = useIntl();
const headerProps = {
title: {
label: formatMessage({ id: 'config-sync.Header.Title' }),
},
content: formatMessage({ id: 'config-sync.Header.Description' }),
};
return ( return (
<Header {...headerProps} /> <Box background="neutral100">
<HeaderLayout
title={formatMessage({ id: 'config-sync.Header.Title' })}
subtitle={formatMessage({ id: 'config-sync.Header.Description' })}
as="h2"
/>
</Box>
); );
}; };

View File

@ -1 +1 @@
export const __DEBUG__ = strapi.env === 'development'; export const __DEBUG__ = true; // TODO: set actual env.

View File

@ -1,8 +0,0 @@
const config = {
blacklist: [
'REDUX_STORAGE_SAVE',
'REDUX_STORAGE_LOAD',
],
};
export default config;

View File

@ -7,7 +7,6 @@
import React from 'react'; import React from 'react';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import ContainerFluid from '../../components/Container';
import Header from '../../components/Header'; import Header from '../../components/Header';
import { store } from "../../helpers/configureStore"; import { store } from "../../helpers/configureStore";
@ -16,10 +15,8 @@ import ConfigPage from '../ConfigPage';
const App = () => { const App = () => {
return ( return (
<Provider store={store}> <Provider store={store}>
<ContainerFluid>
<Header /> <Header />
<ConfigPage /> <ConfigPage />
</ContainerFluid>
</Provider> </Provider>
); );
}; };

View File

@ -21,6 +21,6 @@ const ConfigPage = () => {
<ConfigList isLoading={isLoading} diff={configDiff.toJS()} /> <ConfigList isLoading={isLoading} diff={configDiff.toJS()} />
</div> </div>
); );
} };
export default ConfigPage; export default ConfigPage;

View File

@ -1,14 +1,12 @@
import { createStore, applyMiddleware, compose } from 'redux'; import { createStore, applyMiddleware, compose } from 'redux';
import { createLogger } from 'redux-logger';
import thunkMiddleware from 'redux-thunk'; import thunkMiddleware from 'redux-thunk';
import { Map } from 'immutable'; import { Map } from 'immutable';
import rootReducer from '../state/reducers'; import rootReducer from '../state/reducers';
import loggerConfig from '../config/logger';
import { __DEBUG__ } from '../config/constants'; import { __DEBUG__ } from '../config/constants';
const configureStore = () => { const configureStore = () => {
let initialStoreState = Map(); const initialStoreState = Map();
const enhancers = []; const enhancers = [];
const middlewares = [ const middlewares = [
@ -27,24 +25,6 @@ const configureStore = () => {
if (devtools) { if (devtools) {
console.info('[setup] ✓ Enabling Redux DevTools Extension'); console.info('[setup] ✓ Enabling Redux DevTools Extension');
} }
console.info('[setup] ✓ Enabling state logger');
const loggerMiddleware = createLogger({
level: 'info',
collapsed: true,
stateTransformer: (state) => state.toJS(),
predicate: (getState, action) => {
const state = getState();
const showBlacklisted = state.getIn(['debug', 'logs', 'blacklisted']);
if (loggerConfig.blacklist.indexOf(action.type) !== -1 && !showBlacklisted) {
return false;
}
return state.getIn(['debug', 'logs', 'enabled']);
},
});
middlewares.push(loggerMiddleware);
} }
const composedEnhancers = devtools || compose; const composedEnhancers = devtools || compose;

View File

@ -4,7 +4,7 @@
* *
*/ */
import { request } from 'strapi-helper-plugin'; import { request } from '@strapi/helper-plugin';
import { Map } from 'immutable'; import { Map } from 'immutable';
export function getAllConfigDiff() { export function getAllConfigDiff() {
@ -18,7 +18,7 @@ export function getAllConfigDiff() {
strapi.notification.error('notification.error'); strapi.notification.error('notification.error');
dispatch(setLoadingState(false)); dispatch(setLoadingState(false));
} }
} };
} }
export const SET_CONFIG_DIFF_IN_STATE = 'SET_CONFIG_DIFF_IN_STATE'; export const SET_CONFIG_DIFF_IN_STATE = 'SET_CONFIG_DIFF_IN_STATE';
@ -42,7 +42,7 @@ export function exportAllConfig() {
strapi.notification.error('notification.error'); strapi.notification.error('notification.error');
dispatch(setLoadingState(false)); dispatch(setLoadingState(false));
} }
} };
} }
export function importAllConfig() { export function importAllConfig() {
@ -58,7 +58,7 @@ export function importAllConfig() {
strapi.notification.error('notification.error'); strapi.notification.error('notification.error');
dispatch(setLoadingState(false)); dispatch(setLoadingState(false));
} }
} };
} }
export const SET_LOADING_STATE = 'SET_LOADING_STATE'; export const SET_LOADING_STATE = 'SET_LOADING_STATE';

View File

@ -1006,9 +1006,9 @@
integrity sha512-XmdEOrKQ8a1Y/yxQFOMbC47G/V2VDO1GvMRnl4O75M4GW/abC5tnfzadQYkqEveqRM1dEJGFFegfPNA2vvx2iw== integrity sha512-XmdEOrKQ8a1Y/yxQFOMbC47G/V2VDO1GvMRnl4O75M4GW/abC5tnfzadQYkqEveqRM1dEJGFFegfPNA2vvx2iw==
"@types/node@*": "@types/node@*":
version "16.10.5" version "16.10.9"
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.10.5.tgz#7fe4123b061753f1a58a6cd077ff0bb069ee752d" resolved "https://registry.yarnpkg.com/@types/node/-/node-16.10.9.tgz#8f1cdd517972f76a3b928298f4c0747cd6fef25a"
integrity sha512-9iI3OOlkyOjLQQ9s+itIJNMRepDhB/96jW3fqduJ2FTPQj1dJjw6Q3QCImF9FE1wmdBs5QSun4FjDSFS8d8JLw== integrity sha512-H9ReOt+yqIJPCutkTYjFjlyK6WEMQYT9hLZMlWtOjFQY2ItppsWZ6RJf8Aw+jz5qTYceuHvFgPIaKOHtLAEWBw==
"@types/normalize-package-data@^2.4.0": "@types/normalize-package-data@^2.4.0":
version "2.4.1" version "2.4.1"
@ -1041,9 +1041,9 @@
redux "^4.0.0" redux "^4.0.0"
"@types/react@*", "@types/react@16 || 17": "@types/react@*", "@types/react@16 || 17":
version "17.0.29" version "17.0.30"
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.29.tgz#9535f3fc01a4981ce9cadcf0daa2593c0c2f2251" resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.30.tgz#2f8e6f5ab6415c091cc5e571942ee9064b17609e"
integrity sha512-HSenIfBEBZ70BLrrVhtEtHpqaP79waauPtA8XKlczTxL3hXrW/ElGNLTpD1TmqkykgGlOAK55+D3SmUHEirpFw== integrity sha512-3Dt/A8gd3TCXi2aRe84y7cK1K8G+N9CZRDG8kDGguOKa0kf/ZkSwTmVIDPsm/KbQOVMaDJXwhBtuOXxqwdpWVg==
dependencies: dependencies:
"@types/prop-types" "*" "@types/prop-types" "*"
"@types/scheduler" "*" "@types/scheduler" "*"
@ -1702,9 +1702,9 @@ camelize@^1.0.0:
integrity sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs= integrity sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=
caniuse-lite@^1.0.30001265: caniuse-lite@^1.0.30001265:
version "1.0.30001265" version "1.0.30001267"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001265.tgz#0613c9e6c922e422792e6fcefdf9a3afeee4f8c3" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001267.tgz#b1cf2937175afc0570e4615fc2d2f9069fa0ed30"
integrity sha512-YzBnspggWV5hep1m9Z6sZVLOt7vrju8xWooFAgN6BA5qvy98qPAPb7vNUzypFaoh2pb3vlfzbDO8tB57UPGbtw== integrity sha512-r1mjTzAuJ9W8cPBGbbus8E0SKcUP7gn03R14Wk8FlAlqhH9hroy9nLqmpuXlfKEw/oILW+FGz47ipXV2O7x8lg==
capture-exit@^2.0.0: capture-exit@^2.0.0:
version "2.0.0" version "2.0.0"
@ -2254,9 +2254,9 @@ domexception@^2.0.1:
webidl-conversions "^5.0.0" webidl-conversions "^5.0.0"
electron-to-chromium@^1.3.867: electron-to-chromium@^1.3.867:
version "1.3.867" version "1.3.868"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.867.tgz#7cb484db4b57c28da0b65c51e434c3a1f3f9aa0d" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.868.tgz#ed835023b57ecf0ba63dfe7d50e16b53758ab1da"
integrity sha512-WbTXOv7hsLhjJyl7jBfDkioaY++iVVZomZ4dU6TMe/SzucV6mUAs2VZn/AehBwuZMiNEQDaPuTGn22YK5o+aDw== integrity sha512-kZYCHqwJ1ctGrYDlOcWQH+/AftAm/KD4lEnLDNwS0kKwx1x6dU4zv+GuDjsPPOGn/2TjnKBaZjDyjXaoix0q/A==
elliptic@^6.5.3: elliptic@^6.5.3:
version "6.5.4" version "6.5.4"
@ -2463,9 +2463,9 @@ eslint-loader@2.1.1:
rimraf "^2.6.1" rimraf "^2.6.1"
eslint-module-utils@^2.7.0: eslint-module-utils@^2.7.0:
version "2.7.0" version "2.7.1"
resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.0.tgz#9e97c12688113401259b39d960e6a1f09f966435" resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.1.tgz#b435001c9f8dd4ab7f6d0efcae4b9696d4c24b7c"
integrity sha512-hqSE88MmHl3ru9SYvDyGrlo0JwROlf9fiEMplEV7j/EAuq9iSlIlyCFbBT6pdULQBSnBYtYKiMLps+hKkyP7Gg== integrity sha512-fjoetBXQZq2tSTWZ9yWVl2KuFrTZZH3V+9iD1V1RfpDgxzJR+mPd/KZmMiA8gbPqdBzpNiEHOuT7IYEWxrH0zQ==
dependencies: dependencies:
debug "^3.2.7" debug "^3.2.7"
find-up "^2.1.0" find-up "^2.1.0"
@ -3656,9 +3656,9 @@ istanbul-lib-source-maps@^4.0.0:
source-map "^0.6.1" source-map "^0.6.1"
istanbul-reports@^3.0.2: istanbul-reports@^3.0.2:
version "3.0.4" version "3.0.5"
resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.0.4.tgz#5c38ce8136edf484c0fcfbf7514aafb0363ed1db" resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.0.5.tgz#a2580107e71279ea6d661ddede929ffc6d693384"
integrity sha512-bFjUnc95rHjdCR63WMHUS7yfJJh8T9IPSWavvR02hhjVwezWALZ5axF9EqjmwZHpXqkzbgAMP8DmAtiyNxrdrQ== integrity sha512-5+19PlhnGabNWB7kOFnuxT8H3T/iIyQzIbQMxXsURmmvKg86P2sbkrGOT77VnHw0Qr0gc2XzRaRfMZYYbSQCJQ==
dependencies: dependencies:
html-escaper "^2.0.0" html-escaper "^2.0.0"
istanbul-lib-report "^3.0.0" istanbul-lib-report "^3.0.0"