diff --git a/.eslintrc.js b/.eslintrc.js index 3a2f1dc..798e5da 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -15,5 +15,6 @@ module.exports = { '@typescript-eslint/no-unsafe-assignment': 0, '@typescript-eslint/no-unsafe-call': 0, 'no-warning-comments': ['warn', { terms: ['SECURITY'], location: 'anywhere' }], + '@typescript-eslint/no-unused-variable': [true, { 'ignore-pattern': '^_' }], }, }; diff --git a/app/assets/images/btc-podium.svg b/app/assets/images/btc-podium.svg new file mode 100644 index 0000000..b8a0a57 --- /dev/null +++ b/app/assets/images/btc-podium.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/components/balance-card.tsx b/app/components/balance-card.tsx new file mode 100644 index 0000000..78a1d93 --- /dev/null +++ b/app/components/balance-card.tsx @@ -0,0 +1,25 @@ +import React, { FC } from 'react'; +import { Box, Button, Text } from '@blockstack/ui'; + +interface BalanceCardProps { + balance: string; +} + +export const BalanceCard: FC = ({ balance }) => { + return ( + + + Total balance + + + {balance} + + + + + + + ); +}; diff --git a/app/components/card.tsx b/app/components/card.tsx index 400dd18..72de2d9 100644 --- a/app/components/card.tsx +++ b/app/components/card.tsx @@ -10,7 +10,7 @@ export const Card: React.FC = ({ title, children, ...rest }) => { ( + + + + + + +); diff --git a/app/components/success-checkmark.tsx b/app/components/icons/success-checkmark.tsx similarity index 100% rename from app/components/success-checkmark.tsx rename to app/components/icons/success-checkmark.tsx diff --git a/app/components/stacking-promo-card.tsx b/app/components/stacking-promo-card.tsx new file mode 100644 index 0000000..3d1cb77 --- /dev/null +++ b/app/components/stacking-promo-card.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { Box, Flex, Text, Button } from '@blockstack/ui'; + +export const StackingPromoCard = () => { + return ( + + + + + Earn Bitcoin rewards + + + You’ll earn Bitcoin when you temporarily lock 100,000 STX or more + + + + + ); +}; diff --git a/app/components/stacking-rewards-card.tsx b/app/components/stacking-rewards-card.tsx new file mode 100644 index 0000000..278dda5 --- /dev/null +++ b/app/components/stacking-rewards-card.tsx @@ -0,0 +1,40 @@ +import React, { FC } from 'react'; +import { Box, Flex, Text } from '@blockstack/ui'; +import { Hr } from './hr'; +import { MovementArrow } from './icons/movement-arrow'; + +interface StackingRewardCardProps { + lifetime: string; + lastCycle: string; +} + +export const StackingRewardCard: FC = ({ lifetime, lastCycle }) => { + return ( + + + + Lifetime rewards + + + + {lifetime} + + +
+ + + Last cycle + + + + {lastCycle} + + +
+ ); +}; diff --git a/app/components/toast.tsx b/app/components/toast.tsx index fcfc0ae..e1ea469 100644 --- a/app/components/toast.tsx +++ b/app/components/toast.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Flex, Box, Text } from '@blockstack/ui'; -import { SuccessCheckmark } from './success-checkmark'; +import { SuccessCheckmark } from './icons/success-checkmark'; interface ToastProps { show?: boolean; diff --git a/app/components/transaction-list/transaction-list-empty.tsx b/app/components/transaction-list/transaction-list-empty.tsx new file mode 100644 index 0000000..6721b1f --- /dev/null +++ b/app/components/transaction-list/transaction-list-empty.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { Text, Flex } from '@blockstack/ui'; + +export const TransactionListEmpty = () => { + return ( + + + You haven't made any transactions yet + + + ); +}; diff --git a/app/components/transaction-list/transaction-list.tsx b/app/components/transaction-list/transaction-list.tsx new file mode 100644 index 0000000..4849f42 --- /dev/null +++ b/app/components/transaction-list/transaction-list.tsx @@ -0,0 +1,11 @@ +import React, { FC } from 'react'; +import { TransactionListEmpty } from './transaction-list-empty'; + +interface TransactionListProps { + txs: any[]; +} + +export const TransactionList: FC = ({ txs }) => { + if (txs.length === 0) return ; + return null; +}; diff --git a/app/main.dev.ts b/app/main.dev.ts index 447879f..43d3e7e 100644 --- a/app/main.dev.ts +++ b/app/main.dev.ts @@ -30,21 +30,32 @@ export default class AppUpdater { let mainWindow: BrowserWindow | null = null; if (process.env.NODE_ENV === 'production') { - const sourceMapSupport = require('source-map-support'); - sourceMapSupport.install(); + import('source-map-support') + .then(sourceMapSupport => sourceMapSupport.install()) + .catch(err => { + throw err; + }); } if (process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true') { - require('electron-debug')(); + import('electron-debug') + .then(electronDebug => electronDebug.default()) + .catch(error => console.error(error)); } const installExtensions = async () => { - const installer = require('electron-devtools-installer'); - const forceDownload = !!process.env.UPGRADE_EXTENSIONS; - const extensions = ['REACT_DEVELOPER_TOOLS', 'REDUX_DEVTOOLS']; - return Promise.all( - extensions.map(name => installer.default(installer[name], forceDownload)) - ).catch(console.log); + try { + const installer = await import('electron-devtools-installer'); + const forceDownload = !!process.env.UPGRADE_EXTENSIONS; + const extensions = [installer.REACT_DEVELOPER_TOOLS, installer.REDUX_DEVTOOLS]; + + return Promise.all( + extensions.map(extension => installer.default(extension, forceDownload)) + ).catch(error => new Error(`Error while installing extensions\n${error}`)); + } catch (error) { + console.error(error); + throw error; + } }; const createWindow = async () => { @@ -71,6 +82,8 @@ const createWindow = async () => { void mainWindow.loadURL(`file://${__dirname}/app.html`); + let hasFocusedOnInitialLoad = false; + // @TODO: Use 'ready-to-show' event // https://github.com/electron/electron/blob/master/docs/api/browser-window.md#using-ready-to-show-event mainWindow.webContents.on('did-finish-load', () => { @@ -80,8 +93,10 @@ const createWindow = async () => { if (process.env.START_MINIMIZED) { mainWindow.minimize(); } else { + if (hasFocusedOnInitialLoad) return; mainWindow.show(); mainWindow.focus(); + hasFocusedOnInitialLoad = true; } }); diff --git a/app/pages/home/home-layout.tsx b/app/pages/home/home-layout.tsx new file mode 100644 index 0000000..e407332 --- /dev/null +++ b/app/pages/home/home-layout.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { Flex, Box } from '@blockstack/ui'; + +type HomeComponents = + | 'balanceCard' + | 'transactionList' + | 'stackingPromoCard' + | 'stackingRewardCard'; + +type HomeLayout = { + [key in HomeComponents]: JSX.Element; +}; + +export const HomeLayout: React.FC = ({ + balanceCard, + transactionList, + stackingPromoCard, + stackingRewardCard, +}) => { + return ( + + {balanceCard} + + + {transactionList} + + + {stackingPromoCard} + {stackingRewardCard} + + + {/* */} + {/* Mnemonic: {mnemonic} + MnemonicEncryptedHex: {privateKey} + Private key: {privateKey} + Base58: {base58} + Salt: {(keys as any).salt} + Password: {(keys as any).password} + Stretched Key: {(keys as any).derivedEncryptionKey} */} + + ); +}; diff --git a/app/pages/home/home.tsx b/app/pages/home/home.tsx index 1c4a267..5797e4e 100644 --- a/app/pages/home/home.tsx +++ b/app/pages/home/home.tsx @@ -1,49 +1,40 @@ import React, { useEffect, useState } from 'react'; import { useSelector } from 'react-redux'; -import { Flex, Box } from '@blockstack/ui'; -import { ChainID } from '@blockstack/stacks-transactions'; -import { deriveRootKeychainFromMnemonic, deriveStxAddressChain } from '@blockstack/keychain'; +import { deriveRootKeychainFromMnemonic } from '@blockstack/keychain'; -import { selectMnemonic, selectKeysSlice } from '../../store/keys'; +import { selectMnemonic } from '../../store/keys'; import { BIP32Interface } from '../../types'; +import { TransactionList } from '../../components/transaction-list/transaction-list'; +import { StackingPromoCard } from '../../components/stacking-promo-card'; +import { StackingRewardCard } from '../../components/stacking-rewards-card'; +import { BalanceCard } from '../../components/balance-card'; +import { HomeLayout } from './home-layout'; // // Placeholder component export const Home: React.FC = () => { const mnemonic = useSelector(selectMnemonic); - const keys = useSelector(selectKeysSlice); const [keychain, setKeychain] = useState<{ rootNode: BIP32Interface } | null>(null); useEffect(() => { const deriveMasterKeychain = async () => { if (!mnemonic) return; - const resp = await deriveRootKeychainFromMnemonic(mnemonic, ''); - setKeychain(resp); + const { rootNode } = await deriveRootKeychainFromMnemonic(mnemonic, ''); + setKeychain({ rootNode }); }; void deriveMasterKeychain(); }, [mnemonic]); - if (keychain === null) return
Homepage, but no keychain can be derived
; - - const rootNode = deriveStxAddressChain(ChainID.Testnet)(keychain.rootNode); - - const privateKey = rootNode.privateKey; - - const base58 = rootNode.childKey.toBase58(); - - if (!mnemonic) return <>How you get to homepage without a mnemonic?; - - // console.log(keychain); + if (keychain === null) return <>; return ( - - Mnemonic: {mnemonic} - MnemonicEncryptedHex: {privateKey} - Private key: {privateKey} - Base58: {base58} - Salt: {(keys as any).salt} - Password: {(keys as any).password} - Stretched Key: {(keys as any).derivedEncryptionKey} - + } + transactionList={} + stackingPromoCard={} + stackingRewardCard={ + + } + /> ); }; diff --git a/app/pages/root.tsx b/app/pages/root.tsx index e39e907..e9c8e9a 100644 --- a/app/pages/root.tsx +++ b/app/pages/root.tsx @@ -13,7 +13,8 @@ import { loadFonts } from '../utils/load-fonts'; const GlobalStyle = createGlobalStyle` html, body, #root { - height: 100%; + min-height: 100vh; + max-height: 100vh; } `; diff --git a/app/store/keys/keys.actions.ts b/app/store/keys/keys.actions.ts index 8a24202..8894a4d 100644 --- a/app/store/keys/keys.actions.ts +++ b/app/store/keys/keys.actions.ts @@ -1,9 +1,14 @@ import { push } from 'connected-react-router'; import { createAction, Dispatch } from '@reduxjs/toolkit'; import { useHistory } from 'react-router'; +import log from 'electron-log'; import bcryptjs from 'bcryptjs'; import { memoizeWith, identity } from 'ramda'; -import { generateMnemonicRootKeychain } from '@blockstack/keychain'; +import { + generateMnemonicRootKeychain, + deriveRootKeychainFromMnemonic, + deriveStxAddressChain, +} from '@blockstack/keychain'; import { encryptMnemonic, decryptMnemonic } from 'blockstack'; import routes from '../../constants/routes.json'; @@ -11,8 +16,8 @@ import { MNEMONIC_ENTROPY } from '../../constants'; import { RootState } from '../index'; import { persistSalt, persistEncryptedMnemonic } from '../../utils/disk-store'; import { safeAwait } from '../../utils/safe-await'; -import log from 'electron-log'; -import { selectMnemonic } from './keys.reducer'; +import { selectMnemonic, selectKeysSlice } from './keys.reducer'; +import { ChainID } from '@blockstack/stacks-transactions'; type History = ReturnType; @@ -28,7 +33,7 @@ interface SetPasswordSuccess { } export const setPasswordSuccess = createAction('keys/set-password-success'); -export function onboardingMnemonicGenerationStep({ stepDelayMs }: { stepDelayMs: string }) { +export function onboardingMnemonicGenerationStep({ stepDelayMs }: { stepDelayMs: number }) { return async (dispatch: Dispatch) => { const key = await generateMnemonicRootKeychain(MNEMONIC_ENTROPY); dispatch(persistMnemonicSafe(key.plaintextMnemonic)); @@ -45,29 +50,30 @@ const generateSalt = memoizeWith(identity, async () => await bcryptjs.genSalt(12 export function setPassword({ password, history }: { password: string; history: History }) { return async (dispatch: Dispatch, getState: () => RootState) => { - const state = getState(); - const mnemonic = selectMnemonic(state); + const mnemonic = selectMnemonic(getState()); const salt = await generateSalt(); - const derivedEncryptionKey = await generateDerivedKey({ password, salt }); + if (!mnemonic) { log.error('Cannot derive encryption key unless a mnemonic has been generated'); return; } + const encryptedMnemonicBuffer = await encryptMnemonic(mnemonic, derivedEncryptionKey); const encryptedMnemonic = encryptedMnemonicBuffer.toString('hex'); - // TEMP: to remove, useful for debugging - dispatch(setPasswordSuccess({ salt, encryptedMnemonic })); persistSalt(salt); persistEncryptedMnemonic(encryptedMnemonic); + dispatch(setPasswordSuccess({ salt, encryptedMnemonic })); history.push(routes.HOME); }; } export const attemptWalletDecrypt = createAction('keys/attempt-wallet-decrypt'); -export const attemptWalletDecryptSuccess = createAction<{ salt: string; mnemonic: string }>( - 'keys/attempt-wallet-decrypt-success' -); +export const attemptWalletDecryptSuccess = createAction<{ + salt: string; + mnemonic: string; + address: string; +}>('keys/attempt-wallet-decrypt-success'); export const attemptWalletDecryptFailed = createAction<{ decryptionError: string }>( 'keys/attempt-wallet-decrypt-failed' ); @@ -75,8 +81,13 @@ export const attemptWalletDecryptFailed = createAction<{ decryptionError: string export function decryptWallet({ password, history }: { password: string; history: History }) { return async (dispatch: Dispatch, getState: () => RootState) => { dispatch(attemptWalletDecrypt()); - const salt = (getState().keys as any).salt as string; - const encryptedMnemonic = (getState().keys as any).encryptedMnemonic as string; + const { salt, encryptedMnemonic } = selectKeysSlice(getState()); + + if (!salt || !encryptMnemonic) { + log.error('Cannot decrypt wallet if no `salt` or `encryptedMnemonic` exists'); + return; + } + const key = await generateDerivedKey({ password, salt }); // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -89,8 +100,13 @@ export function decryptWallet({ password, history }: { password: string; history } if (mnemonic) { + const { rootNode } = await deriveRootKeychainFromMnemonic(mnemonic, ''); + console.log({ rootNode }); + const { address } = deriveStxAddressChain(ChainID.Mainnet)(rootNode); + // const pubkey = pubKeyfromPrivKey(rootNode.privateKey); + // console.log({ addressKeychain }); + dispatch(attemptWalletDecryptSuccess({ salt, mnemonic, address })); history.push(routes.HOME); - dispatch(attemptWalletDecryptSuccess({ salt, mnemonic })); } }; } diff --git a/app/store/keys/keys.reducer.spec.ts b/app/store/keys/keys.reducer.spec.ts index 827b68b..d71a952 100644 --- a/app/store/keys/keys.reducer.spec.ts +++ b/app/store/keys/keys.reducer.spec.ts @@ -33,13 +33,4 @@ describe('keysReducer', () => { expect(result).toEqual({ mnemonic: 'test mnemonic' }); }); }); - - describe('persistMnemonic', () => { - test('it saves mnemonic, regardless', () => { - const state = { mnemonic: 'twenty four words blah' } as KeysState; - const action = persistMnemonic('test mnemonic'); - const result = reducer(state, action); - expect(result).toEqual({ mnemonic: 'test mnemonic' }); - }); - }); }); diff --git a/app/store/keys/keys.reducer.ts b/app/store/keys/keys.reducer.ts index 8cd26ad..d954307 100644 --- a/app/store/keys/keys.reducer.ts +++ b/app/store/keys/keys.reducer.ts @@ -15,7 +15,8 @@ export interface KeysState { decrypting: boolean; salt?: string; decryptionError?: string; - encryptMnemonic?: string; + encryptedMnemonic?: string; + address?: string; } const initialState: Readonly = Object.freeze({ @@ -46,7 +47,9 @@ export const createKeysReducer = (keys: Partial = {}) => .addCase(attemptWalletDecryptSuccess, (state, { payload }) => ({ ...state, salt: payload.salt, + decrypting: false, mnemonic: payload.mnemonic, + address: payload.address, })) ); diff --git a/app/utils/safe-await.spec.ts b/app/utils/safe-await.spec.ts new file mode 100644 index 0000000..802482d --- /dev/null +++ b/app/utils/safe-await.spec.ts @@ -0,0 +1,131 @@ +import { safeAwait } from './safe-await'; + +describe('safeAwait()', () => { + test('a valid promise has data', async () => { + const [err, data] = await safeAwait(new Promise(res => res({ objWithData: true }))); + expect(err).toBeUndefined(); + expect(data).toEqual({ objWithData: true }); + }); + + test('error is returned', async () => { + const [err, data] = await safeAwait(new Promise((_res, reject) => reject({ error: true }))); + expect(data).toBeUndefined(); + expect(err).toEqual({ error: true }); + }); + + test('throws on code syntax error "ReferenceError"', async () => { + expect.assertions(1); + try { + const [_err, _data] = await safeAwait( + new Promise(() => { + throw new ReferenceError('a native error'); + }) + ); + } catch (error) { + expect(error).toEqual(new ReferenceError('a native error')); + } + }); + + test('returns error if it is valid response', async () => { + const [err, data] = await safeAwait(new Promise(res => res(new Error('test err')))); + expect(err).toEqual(new Error('test err')); + }); + + test('optional finally function is invoked', async () => { + const mockFn = jest.fn(); + await safeAwait(new Promise(res => res({ objWithData: true })), mockFn); + expect(mockFn).toHaveBeenCalled(); + }); +}); +// import test from 'ava'; +// import safeAwait from '../lib'; + +// test('Valid promise has data. [err, data]', async t => { +// const [err, data] = await safeAwait(promiseOne()); +// t.is(err, undefined); +// t.is(data, 'data here'); +// }); + +// test('Error promise has string error. [err, data]', async t => { +// const [err, data] = await safeAwait(promiseOne(true)); +// t.is(err, 'error happened'); +// t.is(data, undefined); +// }); + +// test('Error promise has instance of error. [err, data]', async t => { +// const [err, data] = await safeAwait(promiseThrows()); +// t.is(err instanceof Error, true); +// t.is(data, undefined); +// }); + +// /** +// * Verify promises still throw native errors when deeper issue exists +// */ + +// test('throws on code syntax error "ReferenceError"', async t => { +// await t.throwsAsync( +// async () => { +// const [err, data] = await safeAwait(promiseWithSyntaxError()); +// }, +// { +// instanceOf: ReferenceError, +// message: 'madeUpThing is not defined', +// } +// ); +// }); + +// test('throws on code syntax error "ReferenceError" in try/catch', async t => { +// try { +// const [err, data] = await safeAwait(promiseWithSyntaxError()); +// } catch (e) { +// t.is(e instanceof ReferenceError, true); +// } +// }); + +// test('throws on code "TypeError"', async t => { +// await t.throwsAsync( +// async () => { +// const [err, data] = await safeAwait(promiseWithTypeError()); +// }, +// { +// instanceOf: TypeError, +// message: "Cannot read property 'lolCool' of null", +// } +// ); +// }); + +// test('throws on code "TypeError" in try/catch', async t => { +// try { +// const [err, data] = await safeAwait(promiseWithTypeError()); +// } catch (e) { +// t.is(e instanceof TypeError, true); +// } +// }); + +// function promiseOne(doError) { +// return new Promise((resolve, reject) => { +// if (doError) return reject('error happened'); // eslint-disable-line +// return resolve('data here'); +// }); +// } + +// function promiseThrows(doError) { +// return new Promise((resolve, reject) => { +// return reject(new Error('business logic error')); +// }); +// } + +// function promiseWithSyntaxError() { +// return new Promise((resolve, reject) => { +// console.log(madeUpThing); +// return resolve('should not reach this'); +// }); +// } + +// function promiseWithTypeError(doError) { +// return new Promise((resolve, reject) => { +// const fakeObject = null; +// console.log(fakeObject.lolCool); +// return resolve('should not reach this'); +// }); +// } diff --git a/jest.config.js b/jest.config.js index 03d0156..2db0e61 100644 --- a/jest.config.js +++ b/jest.config.js @@ -12,4 +12,12 @@ module.exports = { moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx', 'json'], moduleDirectories: ['node_modules', 'app/node_modules'], setupFiles: ['./internals/scripts/CheckBuildsExist.js'], + globals: { + 'ts-jest': { + tsConfig: 'tsconfig.tests.json', + diagnostics: { + ignoreCodes: [6133], + }, + }, + }, }; diff --git a/package.json b/package.json index 4a83620..8a3619c 100644 --- a/package.json +++ b/package.json @@ -110,10 +110,10 @@ "@babel/register": "7.10.1", "@blockstack/eslint-config": "1.0.5", "@blockstack/prettier-config": "0.0.6", - "@blockstack/stacks-transactions": "0.4.6", "@commitlint/config-conventional": "9.0.1", "@types/bcryptjs": "2.4.2", "@types/css-font-loading-module": "0.0.4", + "@types/electron-devtools-installer": "2.2.0", "@types/enzyme": "3.10.5", "@types/enzyme-adapter-react-16": "1.0.6", "@types/history": "4.7.6", @@ -187,6 +187,7 @@ "yarn": "1.22.4" }, "dependencies": { + "@blockstack/stacks-transactions": "0.4.6", "@blockstack/ui": "1.6.3", "@hot-loader/react-dom": "16.13.0", "@reduxjs/toolkit": "1.3.6", diff --git a/tsconfig.json b/tsconfig.json index 23f85e4..09b2cd7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -35,7 +35,8 @@ "app/main.js", "app/main.js.map", "node_modules/**", - "app/node_modules/**" + "app/node_modules/**", + "**/*.spec.ts" ], "typeRoots": ["./node_modules"] } diff --git a/tsconfig.tests.json b/tsconfig.tests.json new file mode 100644 index 0000000..d86f259 --- /dev/null +++ b/tsconfig.tests.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noUnusedLocals": false, + "noUnusedParameters": false + }, + "exclude": [], + "include": ["app/**/*.spec.ts"] +} diff --git a/yarn.lock b/yarn.lock index 0241da2..32c8cf0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1821,6 +1821,11 @@ resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.5.tgz#b14efa8852b7768d898906613c23f688713e02cd" integrity sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ== +"@types/electron-devtools-installer@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@types/electron-devtools-installer/-/electron-devtools-installer-2.2.0.tgz#32ee4ebbe99b3daf9847a6d2097dc00b5de94f10" + integrity sha512-HJNxpaOXuykCK4rQ6FOMxAA0NLFYsf7FiPFGmab0iQmtVBHSAfxzy3MRFpLTTDDWbV0yD2YsHOQvdu8yCqtCfw== + "@types/elliptic@^6.4.12": version "6.4.12" resolved "https://registry.yarnpkg.com/@types/elliptic/-/elliptic-6.4.12.tgz#e8add831f9cc9a88d9d84b3733ff669b68eaa124" @@ -1927,9 +1932,9 @@ integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= "@types/lodash@^4.14.149": - version "4.14.155" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.155.tgz#e2b4514f46a261fd11542e47519c20ebce7bc23a" - integrity sha512-vEcX7S7aPhsBCivxMwAANQburHBtfN9RdyXFk84IJmu2Z4Hkg1tOFgaslRiEqqvoLtbCBi6ika1EMspE+NZ9Lg== + version "4.14.157" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.157.tgz#fdac1c52448861dfde1a2e1515dbc46e54926dc8" + integrity sha512-Ft5BNFmv2pHDgxV5JDsndOWTRJ+56zte0ZpYLowp03tW+K+t8u8YMOzAnpuqPgzX6WO1XpDIUm7u04M8vdDiVQ== "@types/minimatch@*": version "3.0.3" @@ -1976,13 +1981,6 @@ resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24" integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug== -"@types/ramda@0.27.6": - version "0.27.6" - resolved "https://registry.yarnpkg.com/@types/ramda/-/ramda-0.27.6.tgz#5b914266cb73ea61fe3778949ef9b0aa455804b4" - integrity sha512-ephagb0ZIAJSoS5I/qMS4Mqo1b/Nd50pWM+o1QO/dz8NF//GsCGPTLDVRqgXlVncy74KShfHzE5rPZXTeek4PA== - dependencies: - ts-toolbelt "^6.3.3" - "@types/ramda@types/npm-ramda#dist": version "0.25.0" resolved "https://codeload.github.com/types/npm-ramda/tar.gz/9529aa3c8ff70ff84afcbc0be83443c00f30ea90" @@ -4273,12 +4271,11 @@ cross-env@7.0.2: cross-spawn "^7.0.1" cross-fetch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.0.4.tgz#7bef7020207e684a7638ef5f2f698e24d9eb283c" - integrity sha512-MSHgpjQqgbT/94D4CyADeNoYh52zMkCX4pcJvPP5WqPsLFMKjr2TCMg381ox5qI0ii2dPwaLx/00477knXqXVw== + version "3.0.5" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.0.5.tgz#2739d2981892e7ab488a7ad03b92df2816e03f4c" + integrity sha512-FFLcLtraisj5eteosnX1gf01qYDCOc4fDy0+euOt8Kn9YBY2NtXL/pCoYPavw24NIQkQqm5ZOLsGD5Zzj0gyew== dependencies: node-fetch "2.6.0" - whatwg-fetch "3.0.0" cross-spawn@6.0.5, cross-spawn@^6.0.0: version "6.0.5" @@ -12244,11 +12241,6 @@ ts-jest@26.1.1: semver "7.x" yargs-parser "18.x" -ts-toolbelt@^6.3.3: - version "6.9.9" - resolved "https://registry.yarnpkg.com/ts-toolbelt/-/ts-toolbelt-6.9.9.tgz#e6cfd8ec7d425d2a06bda3b4fe9577ceaf2abda8" - integrity sha512-5a8k6qfbrL54N4Dw+i7M6kldrbjgDWb5Vit8DnT+gwThhvqMg8KtxLE5Vmnft+geIgaSOfNJyAcnmmlflS+Vdg== - tsconfig-paths@^3.9.0: version "3.9.0" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b" @@ -12937,11 +12929,6 @@ whatwg-encoding@^1.0.5: dependencies: iconv-lite "0.4.24" -whatwg-fetch@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb" - integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q== - whatwg-mimetype@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf"