From 8108f1d9b02ea711a3a44ceabc3bf440a05d5711 Mon Sep 17 00:00:00 2001 From: Xiphe Date: Tue, 22 Sep 2020 18:20:20 +0200 Subject: [PATCH] refactor: simplify initial state + moneyMoney resources thereby hopefully stabilizing integration tests also update cypress because it went nuts --- cypress/integration/budgetView.spec.ts | 12 +- cypress/integration/createNewBudget.spec.ts | 4 +- cypress/support/commands.ts | 8 +- main/createOpenFileHandler.ts | 5 +- main/defaultMenu.ts | 9 +- main/main.ts | 2 +- main/tsconfig.json | 3 +- main/typings.d.ts | 7 + main/windowManager.ts | 45 +- package-lock.json | 872 ++++++++++--------- package.json | 11 +- src/App.tsx | 83 +- src/__mocks__/electron.ts | 19 +- src/budget/budgetReducer.ts | 32 +- src/budget/getInitData.ts | 34 +- src/budget/index.ts | 6 +- src/budget/useBudgetData.ts | 16 +- src/index.ts | 16 +- src/lib/ShowSettingsContext.tsx | 49 -- src/lib/createResource.ts | 86 +- src/lib/index.ts | 5 - src/lib/useMenu.ts | 21 +- src/lib/useSave.ts | 4 +- src/moneymoney/getAccounts.ts | 12 +- src/moneymoney/getCategories.ts | 8 +- src/moneymoney/getTransactions.ts | 21 +- src/moneymoney/index.ts | 14 +- src/moneymoney/useMoneyMoney.ts | 144 +++ src/moneymoney/useMoneyMoney.tsx | 95 -- src/shared/createMenu.ts | 11 +- src/shared/types.ts | 5 + src/views/Budget/Budget.tsx | 5 +- src/views/Budget/Entry.tsx | 43 - src/views/Budget/index.ts | 2 +- src/views/Main/Main.tsx | 46 + src/views/Main/index.ts | 1 + src/views/Month/Overview.tsx | 4 +- src/views/NewBudget/NewBudget.tsx | 104 +-- src/views/Settings/Categories/Categories.tsx | 11 +- src/views/Settings/General/Account.tsx | 4 +- src/views/Settings/General/StartBalance.tsx | 18 +- src/views/Settings/General/Types.ts | 2 + src/views/Settings/Settings.tsx | 12 +- 43 files changed, 1003 insertions(+), 908 deletions(-) create mode 100644 main/typings.d.ts delete mode 100644 src/lib/ShowSettingsContext.tsx create mode 100644 src/moneymoney/useMoneyMoney.ts delete mode 100644 src/moneymoney/useMoneyMoney.tsx create mode 100644 src/shared/types.ts delete mode 100644 src/views/Budget/Entry.tsx create mode 100644 src/views/Main/Main.tsx create mode 100644 src/views/Main/index.ts diff --git a/cypress/integration/budgetView.spec.ts b/cypress/integration/budgetView.spec.ts index 17c6745..de2bca9 100644 --- a/cypress/integration/budgetView.spec.ts +++ b/cypress/integration/budgetView.spec.ts @@ -8,7 +8,7 @@ import { describe('Budget View', () => { afterEach(() => { - cy.cleanup(); + cy.checkTrailingHandlers(); }); it('displays correct overview values', () => { @@ -42,7 +42,10 @@ describe('Budget View', () => { ], setup({ fs, electron: { ipcMain } }) { fs.writeFileSync(budgetFile, JSON.stringify(myBudget)); - ipcMain.handleOnce('INIT', () => budgetFile); + ipcMain.handleOnce('INIT', () => ({ + type: 'budget', + file: budgetFile, + })); ipcMain.handleOnce('MM_EXPORT_CATEGORIES', () => [ incomeCategory, spendingCategory, @@ -99,7 +102,10 @@ describe('Budget View', () => { ], setup({ fs, electron: { ipcMain } }) { fs.writeFileSync(budgetFile, JSON.stringify(myBudget)); - ipcMain.handleOnce('INIT', () => budgetFile); + ipcMain.handleOnce('INIT', () => ({ + type: 'budget', + file: budgetFile, + })); ipcMain.handleOnce('MM_EXPORT_CATEGORIES', () => [someCategory]); ipcMain.handleOnce('MM_EXPORT_TRANSACTIONS', () => transactions([transaction({ categoryUuid: someCategory.uuid })]), diff --git a/cypress/integration/createNewBudget.spec.ts b/cypress/integration/createNewBudget.spec.ts index 5179c32..b5a4f46 100644 --- a/cypress/integration/createNewBudget.spec.ts +++ b/cypress/integration/createNewBudget.spec.ts @@ -2,7 +2,7 @@ import { account, category } from '../factories'; describe('Create New Budget', () => { afterEach(() => { - cy.cleanup(); + cy.checkTrailingHandlers(); }); it('creates a new budget files with options', () => { @@ -20,7 +20,7 @@ describe('Create New Budget', () => { 'BLUR', ], setup({ electron: { ipcMain } }) { - ipcMain.handleOnce('INIT', () => undefined); + ipcMain.handleOnce('INIT', () => ({ type: 'welcome' })); ipcMain.handleOnce('MM_EXPORT_ACCOUNTS', () => accounts); ipcMain.handleOnce('MM_EXPORT_CATEGORIES', () => categories); ipcMain.handleOnce('MM_EXPORT_TRANSACTIONS', () => []); diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 94f3233..826ab9a 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -5,7 +5,7 @@ import { Exposed as ExposedElectron, ExposedInternal, } from '../../src/__mocks__/electron'; -import { BudgetState, validateBudgetState } from '../../src/budget/Types'; +import { validateBudgetState } from '../../src/budget/Types'; type BB = { electron: ExposedElectron; @@ -26,7 +26,7 @@ declare global { namespace Cypress { interface Chainable { open: (config: OpenConfig) => Cypress.Chainable; - cleanup: () => Cypress.Chainable; + checkTrailingHandlers: () => Cypress.Chainable; bb: () => Cypress.Chainable; _bb: () => Cypress.Chainable; readBudget: (file: string) => Cypress.Chainable; @@ -70,8 +70,8 @@ Cypress.Commands.add('readBudget', (file: string) => { ) .then((data) => validateBudgetState(data)); }); -Cypress.Commands.add('cleanup', () => { - cy._bb().then(({ _electron }) => _electron.cleanup()); +Cypress.Commands.add('checkTrailingHandlers', () => { + cy._bb().then(({ _electron }) => _electron.checkTrailingHandlers()); }); Cypress.Commands.add( 'open', diff --git a/main/createOpenFileHandler.ts b/main/createOpenFileHandler.ts index 0983daa..40393e9 100644 --- a/main/createOpenFileHandler.ts +++ b/main/createOpenFileHandler.ts @@ -1,7 +1,8 @@ import { dialog } from 'electron'; +import { View } from '../src/shared/types'; export default function createOpenFileHandler( - createWindow: (file?: string) => void, + createWindow: (initialView: View) => void, ) { const openFile = async () => { const { canceled, filePaths } = await dialog.showOpenDialog({ @@ -11,7 +12,7 @@ export default function createOpenFileHandler( if (canceled) { return; } - filePaths.forEach(createWindow); + filePaths.forEach((file) => createWindow({ type: 'budget', file })); }; return openFile; diff --git a/main/defaultMenu.ts b/main/defaultMenu.ts index e31312b..16bde59 100644 --- a/main/defaultMenu.ts +++ b/main/defaultMenu.ts @@ -5,9 +5,10 @@ import { createOpenRecent, } from '../src/shared/createMenu'; import { Settings } from '../src/shared/settings'; +import { View } from '../src/shared/types'; export function createDefaultMenu( - createWindow: (file?: string, hash?: string) => void, + createWindow: (initialView: View) => void, openFile: () => void, settings: Settings, ) { @@ -21,14 +22,14 @@ export function createDefaultMenu( createFileMenu({ openRecent: createOpenRecent( settings.getRecentFiles(), - createWindow, + (file) => createWindow({ type: 'budget', file }), ), - fileNew: () => createWindow(undefined, 'new'), + fileNew: () => createWindow({ type: 'new' }), fileOpen: openFile, }), ], { - welcome: () => createWindow(undefined), + welcome: () => createWindow({ type: 'welcome' }), }, ), ), diff --git a/main/main.ts b/main/main.ts index 3ce6c1c..3c445fe 100644 --- a/main/main.ts +++ b/main/main.ts @@ -49,7 +49,7 @@ export default function main() { }); app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) { - windowManager.createWindow(); + windowManager.createWindow({ type: 'welcome' }); } }); } diff --git a/main/tsconfig.json b/main/tsconfig.json index 3e1ed2f..1a5ec97 100644 --- a/main/tsconfig.json +++ b/main/tsconfig.json @@ -1,7 +1,8 @@ { "extends": "../tsconfig.json", "compilerOptions": { - "noEmit": false + "noEmit": false, + "types": ["./typings"] }, "include": ["./*.ts"] } diff --git a/main/typings.d.ts b/main/typings.d.ts new file mode 100644 index 0000000..486e727 --- /dev/null +++ b/main/typings.d.ts @@ -0,0 +1,7 @@ +import type { View } from '../src/shared/types'; + +declare module 'electron' { + interface WebContents { + initialView: View; + } +} diff --git a/main/windowManager.ts b/main/windowManager.ts index bccad31..7dd1d6e 100644 --- a/main/windowManager.ts +++ b/main/windowManager.ts @@ -1,6 +1,14 @@ import { BrowserWindow, IpcMain, WebContents, App } from 'electron'; import { join } from 'path'; import { Settings } from '../src/shared/settings'; +import { View } from '../src/shared/types'; + +// type WebContents = WCT & { +// initialView?: View; +// }; +// type WebContents = Omit & { +// webContents: WebContents; +// }; export type WindowManager = ReturnType; export default function createWindowManager( @@ -71,9 +79,12 @@ export default function createWindowManager( }); } - function createWindow(file?: string, hash?: string) { - if (file && windows[file]) { - windows[file].show(); + function createWindow(initialView: View) { + if ( + (initialView.type === 'budget' || initialView.type === 'settings') && + windows[initialView.file] + ) { + windows[initialView.file].show(); return; } @@ -103,14 +114,15 @@ export default function createWindowManager( if (!SERVER_URL) { throw new Error('Can not open dev window without SERVER_URL'); } - win.loadURL(`${SERVER_URL}#${hash}`); + win.loadURL(`${SERVER_URL}`); win.webContents.openDevTools(); } else { - win.loadFile(join(__dirname, '../build/index.html'), { hash }); + win.loadFile(join(__dirname, '../build/index.html')); } + win.webContents.initialView = initialView; broadcast('WINDOW_CREATED'); - registerWindow(win, file); + registerWindow(win, initialView.file); win.once('closed', (ev: any) => { if (!appIsQuitting) { @@ -119,9 +131,12 @@ export default function createWindowManager( }); } - ipcMain.handle('INIT', (ev) => { - return findFile(ev.sender); - }); + ipcMain.handle( + 'INIT', + (ev): View => { + return ev.sender.initialView; + }, + ); ipcMain.on('QUIT', (ev) => { const win = getWindow(ev.sender); if (!win) { @@ -137,22 +152,24 @@ export default function createWindowManager( win.setDocumentEdited(edited); }); ipcMain.handle('MENU_FILE_NEW', () => { - createWindow(undefined, 'new'); + createWindow({ type: 'new' }); }); ipcMain.handle('MENU_FILE_WELCOME', () => { - createWindow(undefined); + createWindow({ type: 'welcome' }); }); ipcMain.handle('MENU_FILE_OPEN_EXISTING', (_, file: string) => { - createWindow(file); + createWindow({ type: 'budget', file }); }); return { init() { const previouslyOpen = settings.getOpenBudgets(); if (previouslyOpen.length) { - previouslyOpen.forEach((r) => createWindow(r)); + previouslyOpen.forEach((r) => + createWindow({ type: 'budget', file: r }), + ); } else { - createWindow(); + createWindow({ type: 'welcome' }); } }, createWindow, diff --git a/package-lock.json b/package-lock.json index 87e4056..baec8e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1677,7 +1677,6 @@ "version": "1.12.2", "resolved": "https://registry.npmjs.org/@electron/get/-/get-1.12.2.tgz", "integrity": "sha512-vAuHUbfvBQpYTJ5wB7uVIDq5c/Ry0fiTBMs7lnEYAo/qXXppIVcWdfBr57u6eRnKdVso7KSiH6p/LbQAG6Izrg==", - "dev": true, "requires": { "debug": "^4.1.1", "env-paths": "^2.2.0", @@ -2252,15 +2251,37 @@ } }, "@jest/types": { - "version": "25.1.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.1.0.tgz", - "integrity": "sha512-VpOtt7tCrgvamWZh1reVsGADujKigBUFTi19mlRjqEGsE8qH4r3s+skY33dNdXOwyZIvuftZ5tqdF1IgsMejMA==", + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.3.0.tgz", + "integrity": "sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", "@types/yargs": "^15.0.0", - "chalk": "^3.0.0" + "chalk": "^4.0.0" + }, + "dependencies": { + "@types/istanbul-reports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "dev": true, + "requires": { + "@types/istanbul-lib-report": "*" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "@mrmlnc/readdir-enhanced": { @@ -3068,8 +3089,7 @@ "@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", - "dev": true + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==" }, "@svgr/babel-plugin-add-jsx-attribute": { "version": "4.2.0", @@ -3198,33 +3218,179 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", - "dev": true, "requires": { "defer-to-connect": "^1.0.1" } }, "@testing-library/cypress": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@testing-library/cypress/-/cypress-6.0.0.tgz", - "integrity": "sha512-vWPQtPsIDk5STOH2XdJbJoYq9gxOSAItP0ail+MlylK230zNkf3ODKd6eqWnDdruuqrhTF3CyqvPNMA8Xks/UQ==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@testing-library/cypress/-/cypress-7.0.0.tgz", + "integrity": "sha512-sbH3U5yZ7RbN35Lwu/vgA2+XjSbtTSLpp+xFi/c19sdojG82jSYA/gNOCf8/jUKda6W3XwkBHciye7AFTP+qIQ==", "dev": true, "requires": { - "@babel/runtime": "^7.8.7", - "@testing-library/dom": "^7.0.2", - "@types/testing-library__cypress": "^5.0.3" + "@babel/runtime": "^7.11.2", + "@testing-library/dom": "^7.22.2" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", + "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.4" + } + } } }, "@testing-library/dom": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.0.4.tgz", - "integrity": "sha512-+vrLcGDvopLPsBB7JgJhf8ZoOhBSeCsI44PKJL9YoKrP2AvCkqrTg+z77wEEZJ4tSNdxV0kymil7hSvsQQ7jMQ==", + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.24.2.tgz", + "integrity": "sha512-ERxcZSoHx0EcN4HfshySEWmEf5Kkmgi+J7O79yCJ3xggzVlBJ2w/QjJUC+EBkJJ2OeSw48i3IoePN4w8JlVUIA==", "dev": true, "requires": { - "@babel/runtime": "^7.8.4", - "@types/testing-library__dom": "^6.12.1", - "aria-query": "^4.0.2", - "dom-accessibility-api": "^0.3.0", - "pretty-format": "^25.1.0" + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.10.3", + "@types/aria-query": "^4.2.0", + "aria-query": "^4.2.2", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.1", + "pretty-format": "^26.4.2" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } + } + }, + "@babel/runtime": { + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", + "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "@testing-library/jest-dom": { @@ -3583,7 +3749,6 @@ "version": "4.0.6", "resolved": "https://registry.npmjs.org/@types/lodash.debounce/-/lodash.debounce-4.0.6.tgz", "integrity": "sha512-4WTmnnhCfDvvuLMaF3KV4Qfki93KebocUF45msxhYyjMttZDQYzHkO639ohhk8+oco2cluAFL3t5+Jn4mleylQ==", - "dev": true, "requires": { "@types/lodash": "*" } @@ -3592,6 +3757,7 @@ "version": "4.5.5", "resolved": "https://registry.npmjs.org/@types/lodash.isequal/-/lodash.isequal-4.5.5.tgz", "integrity": "sha512-4IKbinG7MGP131wRfceK6W4E/Qt3qssEFLF30LnJbjYiSfHGGRU/Io8YxXrZX109ir+iDETC8hw8QsDijukUVg==", + "dev": true, "requires": { "@types/lodash": "*" } @@ -3909,160 +4075,13 @@ "dev": true }, "@types/testing-library__cypress": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@types/testing-library__cypress/-/testing-library__cypress-5.0.6.tgz", - "integrity": "sha512-TUp5wfanU7zUZigKqIeQDChnHQ1MEzbYqrI5iCQMFiesWNOASWm/el1lFBh1JPqmd6GkdDdDiHYJnkqd9le2ww==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@types/testing-library__cypress/-/testing-library__cypress-5.0.7.tgz", + "integrity": "sha512-4731iHyKpK/kSIpWDGagdujNJqSGQLf0ZMzVI0hPUzzNNgbzm6NHn2px/sdV8qV5oFrYsTl4aQyLA+Yvu3SKpg==", "dev": true, "requires": { "@testing-library/dom": "^7.11.0", "cypress": "*" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "dev": true, - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "@babel/runtime-corejs3": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.11.2.tgz", - "integrity": "sha512-qh5IR+8VgFz83VBa6OkaET6uN/mJOhHONuy3m1sgF0CV6mXdPSEBdA7e1eUbVvyNtANjMbg22JUv71BaDXLY6A==", - "dev": true, - "requires": { - "core-js-pure": "^3.0.0", - "regenerator-runtime": "^0.13.4" - } - }, - "@jest/types": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", - "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^15.0.0", - "chalk": "^3.0.0" - } - }, - "@testing-library/dom": { - "version": "7.22.2", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.22.2.tgz", - "integrity": "sha512-taxURh+4Lwr//uC1Eghat95aMnTlI4G4ETosnZK0wliwHWdutLDVKIvHXAOYdXGdzrBAy1wNhSGmNBbZ72ml4g==", - "dev": true, - "requires": { - "@babel/runtime": "^7.10.3", - "@types/aria-query": "^4.2.0", - "aria-query": "^4.2.2", - "dom-accessibility-api": "^0.5.0", - "pretty-format": "^25.5.0" - } - }, - "aria-query": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", - "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.10.2", - "@babel/runtime-corejs3": "^7.10.2" - } - }, - "dom-accessibility-api": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.0.tgz", - "integrity": "sha512-eCVf9n4Ni5UQAFc2+fqfMPHdtiX7DA0rLakXgNBZfXNJzEbNo3MQIYd+zdYpFBqAaGYVrkd8leNSLGPrG4ODmA==", - "dev": true - }, - "pretty-format": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz", - "integrity": "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==", - "dev": true, - "requires": { - "@jest/types": "^25.5.0", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^16.12.0" - } - } - } - }, - "@types/testing-library__dom": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@types/testing-library__dom/-/testing-library__dom-6.14.0.tgz", - "integrity": "sha512-sMl7OSv0AvMOqn1UJ6j1unPMIHRXen0Ita1ujnMX912rrOcawe4f7wu0Zt9GIQhBhJvH2BaibqFgQ3lP+Pj2hA==", - "dev": true, - "requires": { - "pretty-format": "^24.3.0" - }, - "dependencies": { - "@jest/types": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", - "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^13.0.0" - } - }, - "@types/yargs": { - "version": "13.0.8", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.8.tgz", - "integrity": "sha512-XAvHLwG7UQ+8M4caKIH0ZozIOYay5fQkAgyIXegXT9jPtdIGdhga+sUEdAr1CiG46aB+c64xQEYyEzlwWVTNzA==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "pretty-format": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz", - "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==", - "dev": true, - "requires": { - "@jest/types": "^24.9.0", - "ansi-regex": "^4.0.0", - "ansi-styles": "^3.2.0", - "react-is": "^16.8.4" - } - } } }, "@types/testing-library__jest-dom": { @@ -4747,13 +4766,34 @@ "dev": true }, "aria-query": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.0.2.tgz", - "integrity": "sha512-S1G1V790fTaigUSM/Gd0NngzEfiMy9uTUfMyHhKhVyy4cH5O/eTuR01ydhGL0z4Za1PXFTRGH3qL8VhUQuEO5w==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", "dev": true, "requires": { - "@babel/runtime": "^7.7.4", - "@babel/runtime-corejs3": "^7.7.4" + "@babel/runtime": "^7.10.2", + "@babel/runtime-corejs3": "^7.10.2" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", + "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "@babel/runtime-corejs3": { + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.11.2.tgz", + "integrity": "sha512-qh5IR+8VgFz83VBa6OkaET6uN/mJOhHONuy3m1sgF0CV6mXdPSEBdA7e1eUbVvyNtANjMbg22JUv71BaDXLY6A==", + "dev": true, + "requires": { + "core-js-pure": "^3.0.0", + "regenerator-runtime": "^0.13.4" + } + } } }, "arity-n": { @@ -5684,6 +5724,12 @@ "file-uri-to-path": "1.0.0" } }, + "blob-util": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz", + "integrity": "sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==", + "dev": true + }, "block-stream": { "version": "0.0.9", "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", @@ -5794,7 +5840,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.0.1.tgz", "integrity": "sha512-HRZPIjPcbwAVQvOTxR4YE3o8Xs98NqbbL1iEZDCz7CL8ql0Lt5iOyJFxfnAB0oFs8Oh02F/lLlg30Mexv46LjA==", - "dev": true, "optional": true }, "bottleneck": { @@ -6062,14 +6107,12 @@ "buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", - "dev": true + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" }, "buffer-indexof": { "version": "1.1.1", @@ -6248,7 +6291,6 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", - "dev": true, "requires": { "clone-response": "^1.0.2", "get-stream": "^5.1.0", @@ -6263,7 +6305,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, "requires": { "pump": "^3.0.0" } @@ -6271,8 +6312,7 @@ "lowercase-keys": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" } } }, @@ -6598,14 +6638,14 @@ } }, "cli-table3": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.5.1.tgz", - "integrity": "sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.0.tgz", + "integrity": "sha512-gnB85c3MGC7Nm9I/FkiasNBOKjOiO1RNuXXarQms37q4QMpWdlbBgD/VnOStA2faG1dpXMv31RFApjX1/QdgWQ==", "dev": true, "requires": { "colors": "^1.1.2", "object-assign": "^4.1.0", - "string-width": "^2.1.1" + "string-width": "^4.2.0" } }, "cli-truncate": { @@ -6719,7 +6759,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", - "dev": true, "requires": { "mimic-response": "^1.0.0" } @@ -6977,7 +7016,6 @@ "version": "1.6.2", "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, "requires": { "buffer-from": "^1.0.0", "inherits": "^2.0.3", @@ -7080,7 +7118,6 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==", - "dev": true, "optional": true, "requires": { "ini": "^1.3.4", @@ -7429,8 +7466,7 @@ "core-js": { "version": "3.6.5", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", - "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==", - "dev": true + "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==" }, "core-js-compat": { "version": "3.6.5", @@ -7459,8 +7495,7 @@ "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "cosmiconfig": { "version": "5.2.1", @@ -7898,9 +7933,9 @@ "dev": true }, "cypress": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-4.12.1.tgz", - "integrity": "sha512-9SGIPEmqU8vuRA6xst2CMTYd9sCFCxKSzrHt0wr+w2iAQMCIIsXsQ5Gplns1sT6LDbZcmLv6uehabAOl3fhc9Q==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-5.2.0.tgz", + "integrity": "sha512-9S2spcrpIXrQ+CQIKHsjRoLQyRc2ehB06clJXPXXp1zyOL/uZMM3Qc20ipNki4CcNwY0nBTQZffPbRpODeGYQg==", "dev": true, "requires": { "@cypress/listr-verbose-renderer": "^0.4.1", @@ -7909,26 +7944,27 @@ "@types/sinonjs__fake-timers": "^6.0.1", "@types/sizzle": "^2.3.2", "arch": "^2.1.2", + "blob-util": "2.0.2", "bluebird": "^3.7.2", "cachedir": "^2.3.0", - "chalk": "^2.4.2", + "chalk": "^4.1.0", "check-more-types": "^2.24.0", - "cli-table3": "~0.5.1", + "cli-table3": "~0.6.0", "commander": "^4.1.1", "common-tags": "^1.8.0", "debug": "^4.1.1", "eventemitter2": "^6.4.2", - "execa": "^1.0.0", + "execa": "^4.0.2", "executable": "^4.1.1", "extract-zip": "^1.7.0", - "fs-extra": "^8.1.0", + "fs-extra": "^9.0.1", "getos": "^3.2.1", "is-ci": "^2.0.0", "is-installed-globally": "^0.3.2", "lazy-ass": "^1.6.0", "listr": "^0.14.3", "lodash": "^4.17.19", - "log-symbols": "^3.0.0", + "log-symbols": "^4.0.0", "minimist": "^1.2.5", "moment": "^2.27.0", "ospath": "^1.2.2", @@ -7936,69 +7972,137 @@ "ramda": "~0.26.1", "request-progress": "^3.0.0", "supports-color": "^7.1.0", - "tmp": "~0.1.0", + "tmp": "~0.2.1", "untildify": "^4.0.0", "url": "^0.11.0", "yauzl": "^2.10.0" }, "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "dev": true, "requires": { - "color-convert": "^1.9.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "execa": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.3.tgz", + "integrity": "sha512-WFDXGHckXPWZX19t1kCsXzOpqX9LWYNqn4C+HqZlk/V0imTkzJZqf87ZBhvpHaftERYknpk0fjSylnXVlVgI0A==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + } + }, + "fs-extra": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", + "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", + "dev": true, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^1.0.0" + } + }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" } }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true + }, + "jsonfile": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz", + "integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "dependencies": { - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } + "graceful-fs": "^4.1.6", + "universalify": "^1.0.0" } }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "requires": { - "color-name": "1.1.3" + "path-key": "^3.0.0" } }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, - "has-flag": { + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, - "lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "universalify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", + "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==", "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } } } }, @@ -8110,7 +8214,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", - "dev": true, "requires": { "mimic-response": "^1.0.0" } @@ -8154,14 +8257,12 @@ "defer-to-connect": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", - "dev": true + "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==" }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, "requires": { "object-keys": "^1.0.12" } @@ -8312,8 +8413,7 @@ "detect-node": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", - "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==", - "dev": true + "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==" }, "detect-port-alt": { "version": "1.1.6", @@ -8462,9 +8562,9 @@ } }, "dom-accessibility-api": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.3.0.tgz", - "integrity": "sha512-PzwHEmsRP3IGY4gv/Ug+rMeaTIyTJvadCb+ujYXYeIylbHJezIyNToe8KfEgHTCEYyC+/bUghYOGg8yMGlZ6vA==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.2.tgz", + "integrity": "sha512-k7hRNKAiPJXD2aBqfahSo4/01cTsKWXf+LqJgglnkN2Nz8TsxXKQBXHhKe0Ye9fEfHEZY49uSA5Sr3AqP/sWKA==", "dev": true }, "dom-converter": { @@ -8583,8 +8683,7 @@ "duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", - "dev": true + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" }, "duplexify": { "version": "3.7.1", @@ -8627,7 +8726,6 @@ "version": "9.2.0", "resolved": "https://registry.npmjs.org/electron/-/electron-9.2.0.tgz", "integrity": "sha512-4ecZ3rcGg//Gk4fAK3Jo61T+uh36JhU6HHR/PTujQqQiBw1g4tNPd4R2hGGth2d+7FkRIs5GdRNef7h64fQEMw==", - "dev": true, "requires": { "@electron/get": "^1.0.1", "@types/node": "^12.0.12", @@ -8637,8 +8735,7 @@ "@types/node": { "version": "12.12.54", "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.54.tgz", - "integrity": "sha512-ge4xZ3vSBornVYlDnk7yZ0gK6ChHf/CHB7Gl1I0Jhah8DDnEQqBzgohYG4FX4p81TNirSETOiSyn+y1r9/IR6w==", - "dev": true + "integrity": "sha512-ge4xZ3vSBornVYlDnk7yZ0gK6ChHf/CHB7Gl1I0Jhah8DDnEQqBzgohYG4FX4p81TNirSETOiSyn+y1r9/IR6w==" } } }, @@ -8850,7 +8947,6 @@ "version": "0.3.7", "resolved": "https://registry.npmjs.org/electron-mock-ipc/-/electron-mock-ipc-0.3.7.tgz", "integrity": "sha512-TWNRr0meN28rUHWc3J22LJlW5G1/72OGUAYGknOCEvXANovT3g/eUAjEYpQzEwfMmn1pyl0BYAcCh42gnR0ijw==", - "dev": true, "requires": { "electron": ">=6.0.0" } @@ -9026,14 +9122,12 @@ "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", - "dev": true + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" }, "end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, "requires": { "once": "^1.4.0" } @@ -9164,8 +9258,7 @@ "env-paths": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.0.tgz", - "integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==", - "dev": true + "integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==" }, "errno": { "version": "0.1.7", @@ -9230,7 +9323,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", - "dev": true, "optional": true }, "es6-iterator": { @@ -10410,7 +10502,6 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz", "integrity": "sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==", - "dev": true, "requires": { "concat-stream": "^1.6.2", "debug": "^2.6.9", @@ -10422,7 +10513,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, "requires": { "ms": "2.0.0" } @@ -10431,7 +10521,6 @@ "version": "0.5.5", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, "requires": { "minimist": "^1.2.5" } @@ -10439,8 +10528,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" } } }, @@ -10548,7 +10636,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", - "dev": true, "requires": { "pend": "~1.2.0" } @@ -10908,7 +10995,6 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, "requires": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", @@ -11126,7 +11212,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, "requires": { "pump": "^3.0.0" } @@ -11213,7 +11298,6 @@ "version": "2.1.12", "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-2.1.12.tgz", "integrity": "sha512-caAljRMS/qcDo69X9BfkgrihGUgGx44Fb4QQToNQjsiWh+YlQ66uqYVAdA8Olqit+5Ng0nkz09je3ZzANMZcjg==", - "dev": true, "optional": true, "requires": { "boolean": "^3.0.1", @@ -11229,7 +11313,6 @@ "version": "7.3.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", - "dev": true, "optional": true } } @@ -11275,7 +11358,6 @@ "version": "2.7.1", "resolved": "https://registry.npmjs.org/global-tunnel-ng/-/global-tunnel-ng-2.7.1.tgz", "integrity": "sha512-4s+DyciWBV0eK148wqXxcmVAbFVPqtc3sEtUE/GTQfuU80rySLcMhUmHKSHI7/LDj8q0gDYI1lIhRRB7ieRAqg==", - "dev": true, "optional": true, "requires": { "encodeurl": "^1.0.2", @@ -11294,7 +11376,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.1.tgz", "integrity": "sha512-mJPRTc/P39NH/iNG4mXa9aIhNymaQikTrnspeCa2ZuJ+mH2QN/rXwtX3XwKrHqWgUQFbNZKtHM105aHzJalElw==", - "dev": true, "optional": true, "requires": { "define-properties": "^1.1.3" @@ -11344,7 +11425,6 @@ "version": "9.6.0", "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", - "dev": true, "requires": { "@sindresorhus/is": "^0.14.0", "@szmarczak/http-timer": "^1.1.2", @@ -11729,8 +11809,7 @@ "http-cache-semantics": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", - "dev": true + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" }, "http-deceiver": { "version": "1.2.7", @@ -11971,14 +12050,12 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "dev": true + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" }, "inquirer": { "version": "7.3.3", @@ -12511,8 +12588,7 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isbinaryfile": { "version": "4.0.6", @@ -14799,8 +14875,7 @@ "json-buffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", - "dev": true + "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=" }, "json-parse-better-errors": { "version": "1.0.2", @@ -14838,8 +14913,7 @@ "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, "json3": { "version": "3.3.3", @@ -14902,7 +14976,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", - "dev": true, "requires": { "json-buffer": "3.0.0" } @@ -15357,8 +15430,7 @@ "lodash": { "version": "4.17.15", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, "lodash._reinterpolate": { "version": "3.0.0", @@ -15468,62 +15540,22 @@ "dev": true }, "log-symbols": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", - "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", + "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", "dev": true, "requires": { - "chalk": "^2.4.2" + "chalk": "^4.0.0" }, "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } } } @@ -15579,6 +15611,16 @@ "signal-exit": "^3.0.2" } }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", @@ -15636,8 +15678,7 @@ "lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "dev": true + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" }, "lru-cache": { "version": "6.0.0", @@ -15764,7 +15805,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", - "dev": true, "optional": true, "requires": { "escape-string-regexp": "^4.0.0" @@ -15774,7 +15814,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, "optional": true } } @@ -16053,8 +16092,7 @@ "mimic-response": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" }, "min-indent": { "version": "1.0.1", @@ -16129,8 +16167,7 @@ "minimist": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "minimist-options": { "version": "3.0.2", @@ -16251,9 +16288,9 @@ "dev": true }, "moment": { - "version": "2.27.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz", - "integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ==", + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.0.tgz", + "integrity": "sha512-z6IJ5HXYiuxvFTI6eiQ9dm77uE0gyy1yXNApVHqTcnIKfY9tIwEjlzsZ6u1LQXvVgKeTnv9Xm7NDvJ7lso3MtA==", "dev": true }, "move-concurrently": { @@ -16819,8 +16856,7 @@ "normalize-url": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", - "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", - "dev": true + "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==" }, "npm": { "version": "6.14.8", @@ -20343,7 +20379,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/npm-conf/-/npm-conf-1.1.3.tgz", "integrity": "sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==", - "dev": true, "optional": true, "requires": { "config-chain": "^1.1.11", @@ -20456,8 +20491,7 @@ "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" }, "object-path": { "version": "0.11.4", @@ -20565,7 +20599,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1" } @@ -20689,8 +20722,7 @@ "p-cancelable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", - "dev": true + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==" }, "p-each-series": { "version": "1.0.0", @@ -20961,8 +20993,7 @@ "pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", - "dev": true + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" }, "performance-now": { "version": "2.1.0", @@ -20979,8 +21010,7 @@ "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" }, "pinkie": { "version": "2.0.4", @@ -22387,8 +22417,7 @@ "prepend-http": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", - "dev": true + "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=" }, "pretty-bytes": { "version": "5.3.0", @@ -22407,12 +22436,12 @@ } }, "pretty-format": { - "version": "25.1.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.1.0.tgz", - "integrity": "sha512-46zLRSGLd02Rp+Lhad9zzuNZ+swunitn8zIpfD2B4OPCRLXbM87RJT2aBLBWYOznNUML/2l/ReMyWNC80PJBUQ==", + "version": "26.4.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.4.2.tgz", + "integrity": "sha512-zK6Gd8zDsEiVydOCGLkoBoZuqv8VTiHyAbKznXe/gaph/DAeZOmit9yMfgIz5adIgAMMs5XfoYSwAX3jcCO1tA==", "dev": true, "requires": { - "@jest/types": "^25.1.0", + "@jest/types": "^26.3.0", "ansi-regex": "^5.0.0", "ansi-styles": "^4.0.0", "react-is": "^16.12.0" @@ -22427,14 +22456,12 @@ "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" }, "promise": { "version": "8.1.0", @@ -22476,7 +22503,6 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=", - "dev": true, "optional": true }, "proxy-addr": { @@ -22533,7 +22559,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -23269,7 +23294,6 @@ "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -23819,7 +23843,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", - "dev": true, "requires": { "lowercase-keys": "^1.0.0" } @@ -23944,7 +23967,6 @@ "version": "2.15.3", "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.3.tgz", "integrity": "sha512-AEjYvmAhlyxOeB9OqPUzQCo3kuAkNfuDk/HqWbZdFsqDFpapkTjiw+p4svNEoRLvuqNTxqfL+s+gtD4eDgZ+CA==", - "dev": true, "optional": true, "requires": { "boolean": "^3.0.0", @@ -23994,8 +24016,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-regex": { "version": "1.1.0", @@ -24033,7 +24054,6 @@ "version": "1.6.3", "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz", "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==", - "dev": true, "requires": { "truncate-utf8-bytes": "^1.0.0" } @@ -24577,7 +24597,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", - "dev": true, "optional": true }, "semver-diff": { @@ -24659,7 +24678,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", - "dev": true, "optional": true, "requires": { "type-fest": "^0.13.1" @@ -25315,7 +25333,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", - "dev": true, "optional": true }, "sshpk": { @@ -25514,28 +25531,35 @@ } }, "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", "dev": true, "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" }, "dependencies": { - "ansi-regex": { + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "is-fullwidth-code-point": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true }, "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "^5.0.0" } } } @@ -25578,7 +25602,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, "requires": { "safe-buffer": "~5.1.0" } @@ -25764,7 +25787,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", - "dev": true, "requires": { "debug": "^4.1.0" } @@ -26201,12 +26223,23 @@ "dev": true }, "tmp": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz", - "integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", "dev": true, "requires": { - "rimraf": "^2.6.3" + "rimraf": "^3.0.0" + }, + "dependencies": { + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } } }, "tmpl": { @@ -26239,8 +26272,7 @@ "to-readable-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", - "dev": true + "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==" }, "to-regex": { "version": "3.0.2", @@ -26326,7 +26358,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", "integrity": "sha1-QFkjkJWS1W94pYGENLC3hInKXys=", - "dev": true, "requires": { "utf8-byte-length": "^1.0.1" } @@ -26489,7 +26520,6 @@ "version": "0.0.6", "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", - "dev": true, "optional": true }, "tunnel-agent": { @@ -26525,8 +26555,7 @@ "type-fest": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", - "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", - "dev": true + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==" }, "type-is": { "version": "1.6.18", @@ -26541,8 +26570,7 @@ "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, "typedarray-to-buffer": { "version": "3.1.5", @@ -26676,8 +26704,7 @@ "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" }, "unpipe": { "version": "1.0.0", @@ -26828,7 +26855,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", - "dev": true, "requires": { "prepend-http": "^2.0.0" } @@ -26842,8 +26868,7 @@ "utf8-byte-length": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz", - "integrity": "sha1-9F8VDExm7uloGGUFq5P8u4rWv2E=", - "dev": true + "integrity": "sha1-9F8VDExm7uloGGUFq5P8u4rWv2E=" }, "util": { "version": "0.10.3", @@ -26865,8 +26890,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "util.promisify": { "version": "1.0.1", @@ -28023,8 +28047,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "write": { "version": "1.0.3", @@ -28174,7 +28197,6 @@ "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", - "dev": true, "requires": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" diff --git a/package.json b/package.json index 65d2849..6856217 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,6 @@ ] }, "dependencies": { - "@types/lodash.isequal": "4.5.5", "classnames": "2.2.6", "date-fns": "2.15.0", "electron-log": "4.2.4", @@ -82,7 +81,6 @@ "focus-visible": "5.1.0", "fp-ts": "2.8.1", "io-ts": "2.2.9", - "lodash.debounce": "4.0.8", "lodash.isequal": "4.5.0", "memoize-one": "5.1.1", "plist": "3.0.1", @@ -92,7 +90,7 @@ "devDependencies": { "@craco/craco": "5.6.4", "@semantic-release/exec": "5.0.0", - "@testing-library/cypress": "6.0.0", + "@testing-library/cypress": "7.0.0", "@testing-library/jest-dom": "5.11.3", "@testing-library/react": "10.4.8", "@testing-library/user-event": "12.1.1", @@ -101,21 +99,20 @@ "@types/electron-settings": "3.1.1", "@types/faker": "4.1.12", "@types/jest": "26.0.10", - "@types/lodash.debounce": "4.0.6", + "@types/lodash.isequal": "4.5.5", "@types/node": "14.6.0", "@types/plist": "3.0.2", "@types/pouchdb": "6.4.0", "@types/react": "16.9.46", "@types/react-dom": "16.9.8", - "@types/testing-library__cypress": "5.0.6", + "@types/testing-library__cypress": "5.0.7", "asar": "3.0.3", "concurrently": "5.3.0", - "cypress": "4.12.1", + "cypress": "5.2.0", "cypress-wait-until": "1.7.1", "electron": "9.2.0", "electron-builder": "22.8.0", "electron-builder-notarize": "1.2.0", - "electron-mock-ipc": "0.3.7", "faker": "4.1.0", "get-port-cli": "2.0.0", "memfs": "3.2.0", diff --git a/src/App.tsx b/src/App.tsx index 246c8f6..6bf3e06 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,44 +1,73 @@ import './theme.scss'; -import React, { Suspense, useState, useCallback } from 'react'; +import React, { Suspense, useState, useCallback, ReactNode } from 'react'; import classNames from 'classnames'; -import { INIT_EMPTY, BudgetState, InitRes, getInitData } from './budget'; +import { + InitRes, + getInitData, + useBudgetReducer, + initialInitDataRes, +} from './budget'; import { ErrorBoundary, Startup } from './components'; import styles from './App.module.scss'; import { useRetryResource } from './lib'; +import { useMoneyMoney } from './moneymoney'; -const Budget = React.lazy(() => import('./views/Budget')); const Welcome = React.lazy(() => import('./views/Welcome')); const NewBudget = React.lazy(() => import('./views/NewBudget')); +const Main = React.lazy(() => import('./views/Main')); -function App({ initRes }: { initRes: InitRes }) { - const [initialState, setInitialState] = useState< - typeof INIT_EMPTY | BudgetState - >(initRes.read()); - const [welcome, setWelcome] = useState( - window.location.hash !== '#new', - ); +function App({ readInitialView }: { readInitialView: InitRes }) { + const [initialView, initialState] = readInitialView(); + const [view, setView] = useState(initialView); + const [moneyMoney, updateSettings] = useMoneyMoney(); + const [state, dispatch] = useBudgetReducer(initialState, updateSettings); + const openBudget = useCallback(() => { + setView('budget'); + }, []); + const openNew = useCallback(() => { + setView('new'); + }, []); - return initialState === INIT_EMPTY ? ( - welcome ? ( - setWelcome(false)} /> - ) : ( - - ) - ) : ( - + return ( + + }> + {((): ReactNode => { + switch (view) { + case 'welcome': + return ; + case 'new': + return ( + + ); + default: + return ( +
+ ); + } + })()} + + ); } -export default function AppWrapper({ - initialInitRes, -}: { - initialInitRes: InitRes; -}) { - const [initRes, setInitRes] = useState(initialInitRes); - const retryInitRes = useRetryResource( - initRes, +export default function AppWrapper() { + const [readInit, setInitRes] = useState(() => initialInitDataRes); + const retryReadInit = useRetryResource( + readInit, useCallback(() => setInitRes(getInitData()), []), ); + return (
}> - +
diff --git a/src/__mocks__/electron.ts b/src/__mocks__/electron.ts index 2311815..8c2ede3 100644 --- a/src/__mocks__/electron.ts +++ b/src/__mocks__/electron.ts @@ -74,7 +74,7 @@ function createHandler(name: string) { } return { - cleanup() { + checkTrailingHandlers() { const errors = Object.entries(handlers) .map(([channel, hs]) => { if (hs.length) { @@ -84,7 +84,6 @@ function createHandler(name: string) { }) .filter((v: null | string): v is string => v !== null); - handlers = {}; if (errors.length) { throw new Error(errors.join('\n')); } @@ -140,11 +139,10 @@ function ignoreChannels(channels: string[]) { }); } -function cleanup() { - ignoredChannels.length = 0; - invokeHandlers.cleanup(); - rendererEvents.cleanup(); - mainEvents.cleanup(); +function checkTrailingHandlers() { + invokeHandlers.checkTrailingHandlers(); + rendererEvents.checkTrailingHandlers(); + mainEvents.checkTrailingHandlers(); } export type Exposed = { @@ -152,10 +150,13 @@ export type Exposed = { remote: PartialRemote; }; export type ExposedInternal = { + checkTrailingHandlers: typeof checkTrailingHandlers; ignoreChannels: typeof ignoreChannels; - cleanup: typeof cleanup; }; expose('electron', { ipcMain, remote }); -expose('_electron', { cleanup, ignoreChannels }); +expose('_electron', { + ignoreChannels, + checkTrailingHandlers, +}); export { ipcRenderer, remote }; diff --git a/src/budget/budgetReducer.ts b/src/budget/budgetReducer.ts index 73ee945..6ddc71a 100644 --- a/src/budget/budgetReducer.ts +++ b/src/budget/budgetReducer.ts @@ -1,5 +1,13 @@ -import { BudgetState, Category, IncomeCategory } from './Types'; -import { useReducer } from 'react'; +import { BudgetState, Category, IncomeCategory, VERSION } from './Types'; +import { useCallback, useReducer } from 'react'; +import { initialSettings, unsaved } from '../lib'; + +export const INITIAL_STATE = unsaved({ + name: '', + version: VERSION, + budgets: {}, + settings: initialSettings, +}); export const ACTION_INIT = Symbol('INIT'); export const ACTION_SET_NAME = Symbol('SET_NAME'); @@ -317,6 +325,22 @@ function budgetReducer(state: BudgetState, action: Action): BudgetState { } } -export function useBudgetReducer(initialState: BudgetState) { - return useReducer(budgetReducer, initialState); +export function useBudgetReducer( + initialState: BudgetState, + settingsHook: (settings: BudgetState['settings']) => void, +) { + const hookedReducer = useCallback( + (state: BudgetState, action: Action) => { + const newState = budgetReducer(state, action); + + if (newState.settings !== state.settings) { + settingsHook(newState.settings); + } + + return newState; + }, + [settingsHook], + ); + + return useReducer(hookedReducer, initialState); } diff --git a/src/budget/getInitData.ts b/src/budget/getInitData.ts index 1ecf9cf..05c9ab5 100644 --- a/src/budget/getInitData.ts +++ b/src/budget/getInitData.ts @@ -2,26 +2,32 @@ import { ipcRenderer } from 'electron'; import { readFile as rf } from 'fs'; import { promisify } from 'util'; import { createResource, Resource } from '../lib'; +import { View } from '../shared/types'; +import { INITIAL_STATE } from './budgetReducer'; import { validateBudgetState, BudgetState } from './Types'; const readFile = promisify(rf); -export const INIT_EMPTY = Symbol('INIT_EMPTY'); -export type InitRes = Resource; +type InitData = [View['type'], BudgetState]; +export type InitRes = Resource; -async function getInitData(): Promise { - const response = await ipcRenderer.invoke('INIT'); +async function getInitData(): Promise { + const init: View = await ipcRenderer.invoke('INIT'); - if (typeof response === 'undefined') { - return Promise.resolve(INIT_EMPTY); + switch (init.type) { + case 'welcome': + case 'new': + return [init.type, INITIAL_STATE]; + case 'budget': + case 'settings': + return [ + init.type, + validateBudgetState(JSON.parse((await readFile(init.file)).toString())), + ]; } - if (typeof response === 'string') { - return validateBudgetState( - JSON.parse((await readFile(response)).toString()), - ); - } - throw new Error(`Unexpected init response ${response}`); } -export default function getInitDataRes() { - return createResource(() => getInitData()); +export const initialInitData = getInitData(); +export const initialInitDataRes = createResource(initialInitData); +export default function createInitDataRes() { + return createResource(getInitData()); } diff --git a/src/budget/index.ts b/src/budget/index.ts index 3787b31..f10aa85 100644 --- a/src/budget/index.ts +++ b/src/budget/index.ts @@ -14,7 +14,11 @@ import { InitRes as InitResT } from './getInitData'; export * from './budgetReducer'; export { VERSION } from './Types'; export { default as useBudgetData } from './useBudgetData'; -export { default as getInitData, INIT_EMPTY } from './getInitData'; +export { + default as getInitData, + initialInitDataRes, + initialInitData, +} from './getInitData'; export type InitRes = InitResT; export type Action = ActionT; diff --git a/src/budget/useBudgetData.ts b/src/budget/useBudgetData.ts index 90c5d51..3c17f6e 100644 --- a/src/budget/useBudgetData.ts +++ b/src/budget/useBudgetData.ts @@ -1,11 +1,6 @@ import { useMemo } from 'react'; import { createNumberFormatter } from '../lib'; -import { - useTransactions, - Transaction, - Category, - useCategories, -} from '../moneymoney'; +import type { Transaction, Category, MoneyMoneyRes } from '../moneymoney'; import { BudgetState } from './Types'; import useBudgets from './useBudgets'; @@ -21,15 +16,18 @@ function categoriesLoaded( return Array.isArray(categories); } -export default function useBudgetData(state: BudgetState) { +export default function useBudgetData( + state: BudgetState, + { readCategories, readTransactions }: MoneyMoneyRes, +) { const { fractionDigits, numberLocale, incomeCategories } = state.settings; const numberFormatter = useMemo( () => createNumberFormatter(fractionDigits, numberLocale), [fractionDigits, numberLocale], ); - const transactions = useTransactions().read(); - const [categories, defaultCategories] = useCategories().read(); + const transactions = readTransactions(); + const [categories, defaultCategories] = readCategories(); const usableCategories = useMemo(() => { if (!categoriesLoaded(categories)) { return []; diff --git a/src/index.ts b/src/index.ts index b951211..5c3bda4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,11 +1,6 @@ import './shared/log'; import 'focus-visible'; import initTheme from './initTheme'; -import { InitRes } from './budget/getInitData'; - -async function getInitialInitResource() { - return (await import('./budget/getInitData')).default(); -} const appEntry = document.getElementById('root')!; (async () => { @@ -19,23 +14,16 @@ const appEntry = document.getElementById('root')!; initTheme(); const [ - initialInitRes, { createElement }, { unstable_createRoot }, { default: App }, ] = await Promise.all< - InitRes, typeof import('react'), any, // TODO: use real type once concurrent mode is stable typeof import('react-dom'), typeof import('./App') - >([ - getInitialInitResource(), - import('react'), - import('react-dom'), - import('./App'), - ]); + >([import('react'), import('react-dom'), import('./App')]); - unstable_createRoot(appEntry).render(createElement(App, { initialInitRes })); + unstable_createRoot(appEntry).render(createElement(App)); })().catch((err) => { console.error(err); appEntry.innerHTML = `
diff --git a/src/lib/ShowSettingsContext.tsx b/src/lib/ShowSettingsContext.tsx deleted file mode 100644 index 7348811..0000000 --- a/src/lib/ShowSettingsContext.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import React, { - createContext, - useState, - ReactNode, - useMemo, - ComponentType, - useContext, -} from 'react'; - -type SettingsContextType = [boolean, (showSettings: boolean) => void]; -const ShowSettingsContext = createContext(null); - -function ShowSettingsProvider({ children }: { children: ReactNode }) { - const [showSettings, setShowSettings] = useState(false); - - return ( - [showSettings, setShowSettings], [ - showSettings, - setShowSettings, - ])} - > - {children} - - ); -} - -export function withShowSettingsProvider

( - WrappedComponent: ComponentType

, -) { - return (props: P) => ( - - - - ); -} - -export function useSetShowSettings() { - const showSettings = useContext(ShowSettingsContext); - return showSettings ? showSettings[1] : undefined; -} - -export function useShowSettings() { - const value = useContext(ShowSettingsContext); - if (!value) { - throw new Error('Can not useShowSettings outside of ShowSettingsProvider'); - } - return value[0]; -} diff --git a/src/lib/createResource.ts b/src/lib/createResource.ts index c0dbc52..60edb49 100644 --- a/src/lib/createResource.ts +++ b/src/lib/createResource.ts @@ -1,61 +1,57 @@ import { useMemo } from 'react'; -export type Resource = { - read: () => T; -}; +export type Resource = () => T; -export default function createLazyResource( - init: () => Promise, -): Resource { +export default function createResource( + p: Promise | (() => Promise), +): () => R { let status: 'pending' | 'success' | 'error' = 'pending'; let error: Error; let result: R; let suspender: null | Promise = null; - return { - read(): R { - if (status === 'pending' && suspender === null) { - suspender = init().then( - (r) => { - status = 'success'; - result = r; - }, - (e) => { - status = 'error'; - error = e; - }, - ); - } + const onSuccess = (r: R) => { + status = 'success'; + result = r; + }; + const onError = (e: Error) => { + status = 'error'; + error = e; + }; - switch (status) { - case 'pending': - throw suspender; - case 'error': - throw error; - case 'success': - return result; - } - }, + if (typeof p === 'object') { + suspender = p.then(onSuccess, onError); + } + + return () => { + switch (status) { + case 'pending': + if (suspender === null && typeof p === 'function') { + suspender = p().then(onSuccess, onError); + } + throw suspender; + case 'error': + throw error; + case 'success': + return result; + } }; } export function withRetry( - res: Resource, - { current: retry }: { current?: () => void }, + read: Resource, + retry: () => void, ): Resource { - return { - ...res, - read(...args): R { - try { - return res.read(...args); - } catch (e) { - if (e instanceof Error) { - throw Object.assign(e, { - retry, - }); - } - throw e; + return () => { + try { + return read(); + } catch (e) { + if (e instanceof Error) { + throw Object.assign(e, { + retry, + }); } - }, + throw e; + } }; } @@ -63,5 +59,5 @@ export function useRetryResource( res: Resource, retry: () => void, ): Resource { - return useMemo(() => withRetry(res, { current: retry }), [res, retry]); + return useMemo(() => withRetry(res, retry), [res, retry]); } diff --git a/src/lib/index.ts b/src/lib/index.ts index 116bb94..1c10c53 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -22,11 +22,6 @@ export { default as useSave, unsaved } from './useSave'; export { default as mapCategories } from './mapCategories'; export { default as parseBudgetInput } from './parseBudgetInput'; export { useRecentFiles } from './useRecentFiles'; -export { - withShowSettingsProvider, - useSetShowSettings, - useShowSettings, -} from './ShowSettingsContext'; export { VisibleMothContextProvider, useSetVisibleMonth, diff --git a/src/lib/useMenu.ts b/src/lib/useMenu.ts index 675d64f..d43b818 100644 --- a/src/lib/useMenu.ts +++ b/src/lib/useMenu.ts @@ -1,6 +1,5 @@ -import { useEffect, useMemo, useState, MutableRefObject } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { ipcRenderer, remote, MenuItemConstructorOptions } from 'electron'; -import { useSetShowSettings } from './ShowSettingsContext'; import { createMenu, createFileMenu, @@ -15,15 +14,15 @@ export const MENU_ID_SAVE = 'MENU_SAVE'; export const MENU_ID_SAVE_AS = 'MENU_SAVE_AS'; function buildMenu(callbacks: CreateMenuCallbacks, recentFiles: RecentFile[]) { - const { refreshRef } = callbacks; - const refresh: MenuItemConstructorOptions[] = refreshRef + const { refresh } = callbacks; + const refreshEntry: MenuItemConstructorOptions[] = refresh ? [ { type: 'separator' }, { label: 'Refresh MoneyMoney Data', enabled: true, accelerator: 'CommandOrControl+R', - click: () => refreshRef?.current?.call(null), + click: refresh, }, ] : []; @@ -58,7 +57,7 @@ function buildMenu(callbacks: CreateMenuCallbacks, recentFiles: RecentFile[]) { openRecent: createOpenRecent(recentFiles, (file) => ipcRenderer.invoke('MENU_FILE_OPEN_EXISTING', file), ), - entries: fileMenuEntries.concat(refresh), + entries: fileMenuEntries.concat(refreshEntry), }), createEditMenu(), ], @@ -68,23 +67,23 @@ function buildMenu(callbacks: CreateMenuCallbacks, recentFiles: RecentFile[]) { } export default function useMenu( - refreshRef?: MutableRefObject<(() => void) | undefined>, + refresh?: () => void, + openSettings?: () => void, ) { - const setShowSettings = useSetShowSettings(); const recentFiles = useRecentFiles(); const [focus, updateFocus] = useState(true); const menu = useMemo( () => buildMenu( { - setShowSettings, - refreshRef, + openSettings, + refresh, welcome: () => ipcRenderer.invoke('MENU_FILE_WELCOME'), }, recentFiles, ), /* eslint-disable-next-line react-hooks/exhaustive-deps */ - [setShowSettings, refreshRef, recentFiles], + [openSettings, refresh, recentFiles], ); useEffect(() => { const setFocus = () => updateFocus(true); diff --git a/src/lib/useSave.ts b/src/lib/useSave.ts index b306996..bf739cd 100644 --- a/src/lib/useSave.ts +++ b/src/lib/useSave.ts @@ -120,5 +120,7 @@ export default function useSave(menu: Menu, state: BudgetState | null) { } enable(true); }, [enable, state, saved]); - return isError(saved) ? saved : null; + if (isError(saved)) { + throw saved; + } } diff --git a/src/moneymoney/getAccounts.ts b/src/moneymoney/getAccounts.ts index 7a1fcf8..0ac62c2 100644 --- a/src/moneymoney/getAccounts.ts +++ b/src/moneymoney/getAccounts.ts @@ -3,7 +3,7 @@ import { ipcRenderer } from 'electron'; import { createResource, Resource } from '../lib'; import memoizeOne from 'memoize-one'; -const filterAccounts = memoizeOne( +export const filterAccounts = memoizeOne( (currency: string, interopAccounts: InteropAccount[]): Account[] => { return interopAccounts .map( @@ -50,10 +50,8 @@ export async function getAccounts(): Promise { return probablyAccounts.map(validateAccount); } -export default function getAccountsResource( - currency: string, -): AccountsResource { - return createResource(() => - getAccounts().then(filterAccounts.bind(null, currency)), - ); +export default function getInteropAccountsResource(): Resource< + InteropAccount[] +> { + return createResource(async () => getAccounts()); } diff --git a/src/moneymoney/getCategories.ts b/src/moneymoney/getCategories.ts index 0014876..6ee2554 100644 --- a/src/moneymoney/getCategories.ts +++ b/src/moneymoney/getCategories.ts @@ -6,7 +6,7 @@ import { createResource, Resource } from '../lib'; type SplitCategories = [Category[], Category[]]; export type CategoryResource = Resource; -const splitCurrencyCategories = memoizeOne( +export const splitCurrencyCategories = memoizeOne( (currency: string, categories: Category[]) => categories.reduce( (memo, category): SplitCategories => { @@ -34,8 +34,6 @@ export async function getCategories(): Promise { return probablyCategories.map(validateCategory); } -export default function getCategoryResource(currency: string) { - return createResource(() => - getCategories().then(splitCurrencyCategories.bind(null, currency)), - ); +export default function getCategoryResource(): Resource { + return createResource(() => getCategories()); } diff --git a/src/moneymoney/getTransactions.ts b/src/moneymoney/getTransactions.ts index 7fd3ab5..44f8f6d 100644 --- a/src/moneymoney/getTransactions.ts +++ b/src/moneymoney/getTransactions.ts @@ -6,11 +6,12 @@ import memoizeOne from 'memoize-one'; export type TransactionsResource = Resource; -const filterCurrency = memoizeOne( +export const filterCurrency = memoizeOne( (currency: string, transactions: Transaction[]) => transactions.filter(({ currency: c }) => c === currency), ); -export async function getTransactions( + +async function getTransactions( accountNumbers: string[], startDateTimestamp: number, ): Promise { @@ -34,13 +35,13 @@ export async function getTransactions( } export default function getTransactionsResource( - accountNumbers: string[], - currency: string, - startDateTimestamp: number, + p: Promise<{ + accounts: string[]; + startDate: number; + }>, ): TransactionsResource { - return createResource(() => - getTransactions(accountNumbers, startDateTimestamp).then( - filterCurrency.bind(null, currency), - ), - ); + return createResource(async () => { + const { accounts, startDate } = await p; + return getTransactions(accounts, startDate); + }); } diff --git a/src/moneymoney/index.ts b/src/moneymoney/index.ts index 8840d25..68f20dd 100644 --- a/src/moneymoney/index.ts +++ b/src/moneymoney/index.ts @@ -7,17 +7,10 @@ import { Balance as BalanceT, } from './Types'; import { AccountsResource as AccountsResourceT } from './getAccounts'; +import { MoneyMoneyRes as MoneyMoneyResT } from './useMoneyMoney'; export { default as getAccounts } from './getAccounts'; -export { - default as getTransactionsRes, - getTransactions, -} from './getTransactions'; -export { - useAccounts, - useCategories, - useTransactions, - default as MoneyMoneyResProvider, -} from './useMoneyMoney'; +export { default as getTransactionsRes } from './getTransactions'; +export { useMoneyMoney } from './useMoneyMoney'; export { default as calculateBalances } from './calculateBalances'; export * from './errors'; export type AccountsResource = AccountsResourceT; @@ -27,3 +20,4 @@ export type Category = CategoryT; export type Balances = BalancesT; export type Balance = BalanceT; export type AmountWithTransactions = AmountWithTransactionsT; +export type MoneyMoneyRes = MoneyMoneyResT; diff --git a/src/moneymoney/useMoneyMoney.ts b/src/moneymoney/useMoneyMoney.ts new file mode 100644 index 0000000..88d86c4 --- /dev/null +++ b/src/moneymoney/useMoneyMoney.ts @@ -0,0 +1,144 @@ +import { useMemo, useCallback, useState, useRef } from 'react'; +import getTransactions, { + TransactionsResource, + filterCurrency, +} from './getTransactions'; +import getCategories, { + CategoryResource, + splitCurrencyCategories, +} from './getCategories'; +import getAccounts, { AccountsResource, filterAccounts } from './getAccounts'; +import { BudgetState } from '../budget'; +import { initialInitData } from '../budget/getInitData'; +import { useRetryResource } from '../lib'; + +export type MoneyMoneyRes = { + readCategories: CategoryResource; + readTransactions: TransactionsResource; + readAccounts: AccountsResource; + refresh: () => void; +}; + +type RequiredSettings = Pick< + BudgetState['settings'], + 'accounts' | 'currency' | 'startDate' +>; + +let initialSettings: RequiredSettings | null; +const pSettings = initialInitData.then(([_, { settings }]) => { + initialSettings = settings; + return settings; +}); +const initialAccountsRes = getAccounts(); +const initialCategoriesRes = getCategories(); +const initialTransactionsRes = getTransactions(pSettings); + +function ignoreError(cb: () => void) { + try { + cb(); + } catch (err) { + /* ¯\_(ツ)_/¯ */ + } +} + +initialInitData.then(([initialView]) => { + switch (initialView) { + case 'budget': + ignoreError(() => initialCategoriesRes()); + ignoreError(() => initialTransactionsRes()); + break; + case 'settings': + case 'new': + ignoreError(() => initialAccountsRes()); + break; + } +}); + +export function useMoneyMoney(): [ + MoneyMoneyRes, + (settings: RequiredSettings) => void, +] { + if (!initialSettings) { + throw new Error( + 'Unexpected useMoneyMoney call before initialSettings are resolved', + ); + } + + const [currency, setCurrency] = useState( + initialSettings.currency, + ); + + const settingsRef = useRef(initialSettings); + + const [readAccounts, setAccountsRes] = useState(() => initialAccountsRes); + const [readCategories, setCategoriesRes] = useState( + () => initialCategoriesRes, + ); + const [readTransactions, setTransactionsRes] = useState( + () => initialTransactionsRes, + ); + + const refreshAll = useCallback(() => { + const newAccountsRes = getAccounts(); + setAccountsRes(() => newAccountsRes); + + const newCategoriesRes = getCategories(); + setCategoriesRes(() => newCategoriesRes); + + const newTransactionsRes = getTransactions( + Promise.resolve(settingsRef.current), + ); + setTransactionsRes(() => newTransactionsRes); + }, []); + + const readRetryAccounts = useRetryResource(readAccounts, refreshAll); + const readFilteredAccounts = useCallback( + () => filterAccounts(currency, readRetryAccounts()), + [currency, readRetryAccounts], + ); + + const readRetryCategories = useRetryResource(readCategories, refreshAll); + const readSplitCategories = useCallback( + () => splitCurrencyCategories(currency, readRetryCategories()), + [currency, readRetryCategories], + ); + + const readRetryTransactions = useRetryResource(readTransactions, refreshAll); + const readFilteredTransactions = useCallback( + () => filterCurrency(currency, readRetryTransactions()), + [currency, readRetryTransactions], + ); + + const updateSettings = useCallback((newSettings: RequiredSettings) => { + setCurrency(newSettings.currency); + const oldSettings = settingsRef.current; + settingsRef.current = newSettings; + if ( + oldSettings.accounts !== newSettings.accounts || + oldSettings.startDate !== newSettings.startDate + ) { + const newTransactionsRes = getTransactions( + Promise.resolve(settingsRef.current), + ); + setTimeout(() => { + setTransactionsRes(() => newTransactionsRes); + }, 10); + } + }, []); + + const moneyMoney = useMemo( + (): MoneyMoneyRes => ({ + readAccounts: readFilteredAccounts, + readCategories: readSplitCategories, + readTransactions: readFilteredTransactions, + refresh: refreshAll, + }), + [ + readFilteredAccounts, + readSplitCategories, + readFilteredTransactions, + refreshAll, + ], + ); + return [moneyMoney, updateSettings]; +} diff --git a/src/moneymoney/useMoneyMoney.tsx b/src/moneymoney/useMoneyMoney.tsx deleted file mode 100644 index a80f8a5..0000000 --- a/src/moneymoney/useMoneyMoney.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import React, { - useMemo, - useCallback, - createContext, - ReactNode, - useState, - Suspense, - useContext, - SuspenseProps, - MutableRefObject, -} from 'react'; -import getTransactions, { TransactionsResource } from './getTransactions'; -import getCategories, { CategoryResource } from './getCategories'; -import getAccounts, { AccountsResource } from './getAccounts'; -import { ErrorBoundary } from '../components'; -import { BudgetState } from '../budget'; -import { withRetry } from '../lib'; - -type MoneyMoneyContext = { - categoriesRes: CategoryResource; - transactionRes: TransactionsResource; - accountsRes: AccountsResource; -}; -const MoneyMoneyResContext = createContext>({}); - -type ProviderProps = { - refreshRef: MutableRefObject<(() => void) | undefined>; - settings: BudgetState['settings']; - fallback: SuspenseProps['fallback']; - children: ReactNode; -}; -export default function MoneyMoneyResProvider({ - refreshRef, - settings: { accounts, currency, startDate }, - fallback, - children, -}: ProviderProps) { - const refresh = useCallback(() => { - setCategoriesRes(withRetry(getCategories(currency), refreshRef)); - setTransactionRes( - withRetry(getTransactions(accounts, currency, startDate), refreshRef), - ); - setAccountsRes(withRetry(getAccounts(currency), refreshRef)); - }, [refreshRef, accounts, currency, startDate]); - refreshRef.current = refresh; - - const [categoriesRes, setCategoriesRes] = useState(() => - withRetry(getCategories(currency), refreshRef), - ); - const [transactionRes, setTransactionRes] = useState( - () => withRetry(getTransactions(accounts, currency, startDate), refreshRef), - ); - const [accountsRes, setAccountsRes] = useState(() => - withRetry(getAccounts(currency), refreshRef), - ); - - return ( - ( - () => ({ - accountsRes, - transactionRes, - categoriesRes, - }), - [accountsRes, transactionRes, categoriesRes], - )} - > - - {children} - - - ); -} - -export function useAccounts() { - const { accountsRes } = useContext(MoneyMoneyResContext); - if (!accountsRes) { - throw new Error('Can not useAccounts outside of MoneyMoneyResContext'); - } - return accountsRes; -} -export function useCategories() { - const { categoriesRes } = useContext(MoneyMoneyResContext); - if (!categoriesRes) { - throw new Error('Can not useCategories outside of MoneyMoneyResContext'); - } - return categoriesRes; -} -export function useTransactions() { - const { transactionRes } = useContext(MoneyMoneyResContext); - if (!transactionRes) { - throw new Error('Can not useTransactions outside of MoneyMoneyResContext'); - } - return transactionRes; -} diff --git a/src/shared/createMenu.ts b/src/shared/createMenu.ts index f6a7b8a..f44111d 100644 --- a/src/shared/createMenu.ts +++ b/src/shared/createMenu.ts @@ -1,13 +1,12 @@ import { MenuItemConstructorOptions } from 'electron'; import { basename } from 'path'; import { RecentFile } from './settings'; -import { MutableRefObject } from 'react'; type MenuConfig = MenuItemConstructorOptions; export type CreateMenuCallbacks = { - refreshRef?: MutableRefObject<(() => void) | undefined>; + refresh?: () => void; welcome: () => void; - setShowSettings?: (show: boolean) => void; + openSettings?: () => void; }; function isMenuConfig( @@ -19,16 +18,16 @@ function isMenuConfig( export function createMenu( appName: string, entries: MenuConfig[] = [], - { setShowSettings, welcome }: CreateMenuCallbacks, + { openSettings, welcome }: CreateMenuCallbacks, ): MenuConfig[] { const submenu: (MenuConfig | false | undefined)[] = [ { role: 'about' }, { label: 'Open Welcome', click: welcome }, { type: 'separator' }, - setShowSettings && { + openSettings && { label: 'Settings', accelerator: 'CommandOrControl+,', - click: () => setShowSettings(true), + click: () => openSettings(), }, { type: 'separator' }, { role: 'services' }, diff --git a/src/shared/types.ts b/src/shared/types.ts new file mode 100644 index 0000000..9acea3b --- /dev/null +++ b/src/shared/types.ts @@ -0,0 +1,5 @@ +export type View = + | { type: 'new'; file?: never } + | { type: 'welcome'; file?: never } + | { type: 'budget'; file: string } + | { type: 'settings'; file: string }; diff --git a/src/views/Budget/Budget.tsx b/src/views/Budget/Budget.tsx index 817d700..dee3784 100644 --- a/src/views/Budget/Budget.tsx +++ b/src/views/Budget/Budget.tsx @@ -19,13 +19,15 @@ import Month from '../Month'; import BudgetHeader from './Header'; import CategorySidebar from '../CategorySidebar/CategorySidebar'; import styles from './Budget.module.scss'; +import { MoneyMoneyRes } from '../../moneymoney'; type Props = { state: BudgetState; dispatch: Dispatch; + moneyMoney: MoneyMoneyRes; }; -export default function Budget({ state, dispatch }: Props) { +export default function Budget({ state, dispatch, moneyMoney }: Props) { const sidebarRef = useRef(null); const sliderRef = useRef(null); const onSliderScrollRef = useRef<((target: HTMLDivElement) => void) | null>( @@ -34,6 +36,7 @@ export default function Budget({ state, dispatch }: Props) { const [scrollTo, setScrollTo] = useState(null); const { months, numberFormatter, extendFuture, categories } = useBudgetData( state, + moneyMoney, ); const handleHeaderMonthClick = useCallback( (key: string) => { diff --git a/src/views/Budget/Entry.tsx b/src/views/Budget/Entry.tsx deleted file mode 100644 index 2d0a0eb..0000000 --- a/src/views/Budget/Entry.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React, { lazy, useRef } from 'react'; -import { BudgetState, useBudgetReducer } from '../../budget'; -import { - withShowSettingsProvider, - useShowSettings, - useMenu, - useSave, -} from '../../lib'; -import { Startup } from '../../components'; -import { MoneyMoneyResProvider } from '../../moneymoney'; - -type Props = { - initialState: BudgetState; -}; - -const Settings = lazy(() => import('../Settings')); -const Budget = lazy(() => import('./Budget')); - -export default withShowSettingsProvider(({ initialState }: Props) => { - const [state, dispatch] = useBudgetReducer(initialState); - const refreshRef = useRef<() => void>(); - const menu = useMenu(refreshRef); - const error = useSave(menu, state); - const showSettings = useShowSettings(); - - if (error) { - throw error; - } - - return ( - } - > - {showSettings ? ( - - ) : ( - - )} - - ); -}); diff --git a/src/views/Budget/index.ts b/src/views/Budget/index.ts index ba27932..b414a90 100644 --- a/src/views/Budget/index.ts +++ b/src/views/Budget/index.ts @@ -1 +1 @@ -export { default } from './Entry'; +export { default } from './Budget'; diff --git a/src/views/Main/Main.tsx b/src/views/Main/Main.tsx new file mode 100644 index 0000000..d2ae4ff --- /dev/null +++ b/src/views/Main/Main.tsx @@ -0,0 +1,46 @@ +import React, { Dispatch, useCallback } from 'react'; +import { Action, BudgetState } from '../../budget'; +import { useMenu, useSave } from '../../lib'; +import { MoneyMoneyRes } from '../../moneymoney'; + +const Settings = React.lazy(() => import('../Settings')); +const Budget = React.lazy(() => import('../Budget')); + +type MainViewProps = { + view: 'budget' | 'settings'; + state: BudgetState; + moneyMoney: MoneyMoneyRes; + dispatch: Dispatch; + setView: (view: 'budget' | 'settings') => void; +}; +export default function MainView({ + moneyMoney, + state, + dispatch, + view, + setView, +}: MainViewProps) { + const openSettings = useCallback(() => { + setView('settings'); + }, [setView]); + const openBudget = useCallback(() => { + setView('budget'); + }, [setView]); + useSave(useMenu(moneyMoney.refresh, openSettings), state); + + switch (view) { + case 'budget': + return ( + + ); + case 'settings': + return ( + + ); + } +} diff --git a/src/views/Main/index.ts b/src/views/Main/index.ts new file mode 100644 index 0000000..47dec62 --- /dev/null +++ b/src/views/Main/index.ts @@ -0,0 +1 @@ +export { default } from './Main'; diff --git a/src/views/Month/Overview.tsx b/src/views/Month/Overview.tsx index 5cdb6bb..3738c4f 100644 --- a/src/views/Month/Overview.tsx +++ b/src/views/Month/Overview.tsx @@ -41,7 +41,9 @@ export default function Overview({ requestAnimationFrame(() => { cleanup(); cleanup = registerHeaderHeight( - Math.ceil(ref.current!.getBoundingClientRect().height), + Math.ceil( + ref.current ? ref.current.getBoundingClientRect().height : 0, + ), ); }); }; diff --git a/src/views/NewBudget/NewBudget.tsx b/src/views/NewBudget/NewBudget.tsx index 6f60039..f54858d 100644 --- a/src/views/NewBudget/NewBudget.tsx +++ b/src/views/NewBudget/NewBudget.tsx @@ -1,77 +1,61 @@ -import React, { useState, useRef } from 'react'; -import { BudgetState, VERSION } from '../../budget'; -import { - Content, - Button, - Header, - HeaderSpacer, - Startup, -} from '../../components'; +import React, { useState, Dispatch } from 'react'; +import { Action, BudgetState } from '../../budget'; +import { Content, Button, Header, HeaderSpacer } from '../../components'; import General from '../Settings/General'; import Categories from '../Settings/Categories'; -import { useBudgetReducer } from '../../budget'; import useMenu from '../../lib/useMenu'; -import { initialSettings, unsaved } from '../../lib'; -import { MoneyMoneyResProvider } from '../../moneymoney'; +import { MoneyMoneyRes } from '../../moneymoney'; type Props = { - onCreate: (budget: BudgetState) => void; + state: BudgetState; + dispatch: Dispatch; + moneyMoney: MoneyMoneyRes; + onCreate: () => void; }; -export default function NewBudget({ onCreate }: Props) { +export default function NewBudget({ + onCreate, + state, + dispatch, + moneyMoney, +}: Props) { const [page, setPage] = useState<'general' | 'categories'>('general'); - const refreshRef = useRef<() => void>(); - useMenu(refreshRef); - const [state, dispatch] = useBudgetReducer({ - name: '', - version: VERSION, - budgets: {}, - settings: initialSettings, - }); + useMenu(moneyMoney.refresh); if (state === null) { throw new Error('Unexpected non-initialized state'); } return ( - } + + Create a new Budget + + {page === 'general' && ( + + )} + {page === 'categories' && ( + + )} + + } > - - Create a new Budget - - {page === 'general' && ( - - )} - {page === 'categories' && ( - - )} - - } - > - {page === 'general' && } - {page === 'categories' && ( - - )} - - + {page === 'general' && ( + + )} + {page === 'categories' && ( + + )} + ); } diff --git a/src/views/Settings/Categories/Categories.tsx b/src/views/Settings/Categories/Categories.tsx index 68183d8..8976b21 100644 --- a/src/views/Settings/Categories/Categories.tsx +++ b/src/views/Settings/Categories/Categories.tsx @@ -1,10 +1,15 @@ import React from 'react'; -import { useCategories } from '../../../moneymoney'; import { Props } from './Types'; import IncomeCategories from './IncomeCategories'; +import { MoneyMoneyRes } from '../../../moneymoney'; -export default function CategorySettings(props: Omit) { - const [categories] = useCategories().read(); +export default function CategorySettings({ + moneyMoney: { readCategories }, + ...props +}: Omit & { + moneyMoney: MoneyMoneyRes; +}) { + const [categories] = readCategories(); return ; } diff --git a/src/views/Settings/General/Account.tsx b/src/views/Settings/General/Account.tsx index df71632..e8492b5 100644 --- a/src/views/Settings/General/Account.tsx +++ b/src/views/Settings/General/Account.tsx @@ -4,15 +4,15 @@ import { useInputProps } from '../../../lib'; import styles from '../Settings.module.scss'; import { Props } from './Types'; import { ACTION_SETTINGS_SET_SELECTED_ACCOUNTS } from '../../../budget'; -import { useAccounts } from '../../../moneymoney'; export default function AccountSettings({ dispatch, state: { settings: { accounts }, }, + moneyMoney: { readAccounts }, }: Props) { - const allAccounts = useAccounts().read(); + const allAccounts = readAccounts(); const { value, onChange, error, ...rest } = useInputProps({ internal: false, value: accounts, diff --git a/src/views/Settings/General/StartBalance.tsx b/src/views/Settings/General/StartBalance.tsx index 9db4f9f..fbbdc1d 100644 --- a/src/views/Settings/General/StartBalance.tsx +++ b/src/views/Settings/General/StartBalance.tsx @@ -6,17 +6,19 @@ import Input from '../Input'; import Setting from '../Setting'; import styles from '../Settings.module.scss'; import { Props } from './Types'; -import { useTransactions, useAccounts } from '../../../moneymoney'; +import { MoneyMoneyRes } from '../../../moneymoney'; function RecalculateButton({ settings: { accounts }, update, -}: { + readAccounts, + readTransactions, +}: Pick & { settings: Props['state']['settings']; update: (payload: number) => void; }) { - const allAccounts = useAccounts().read(); - const transactions = useTransactions().read(); + const allAccounts = readAccounts(); + const transactions = readTransactions(); const recalculate = useCallback(() => { const transactionsSum = transactions.reduce( (memo, { amount }) => memo + amount, @@ -41,7 +43,9 @@ export default function StartBalanceSetting({ state: { settings }, dispatch, numberFormatter, + moneyMoney, }: Props & { + moneyMoney: MoneyMoneyRes; numberFormatter: NumberFormatter; }) { const update = useCallback( @@ -73,7 +77,11 @@ export default function StartBalanceSetting({ } > - + ); diff --git a/src/views/Settings/General/Types.ts b/src/views/Settings/General/Types.ts index 921c027..64430a8 100644 --- a/src/views/Settings/General/Types.ts +++ b/src/views/Settings/General/Types.ts @@ -1,7 +1,9 @@ import { Dispatch } from 'react'; import { BudgetState, Action } from '../../../budget'; +import { MoneyMoneyRes } from '../../../moneymoney'; export type Props = { + moneyMoney: MoneyMoneyRes; state: BudgetState; dispatch: Dispatch; }; diff --git a/src/views/Settings/Settings.tsx b/src/views/Settings/Settings.tsx index bd24fa6..108c3c4 100644 --- a/src/views/Settings/Settings.tsx +++ b/src/views/Settings/Settings.tsx @@ -1,18 +1,20 @@ import React, { Dispatch, useState } from 'react'; import { BudgetState, Action } from '../../budget'; import { Content, Tab, TabBar, Header, Button } from '../../components'; -import { useSetShowSettings } from '../../lib'; import General from './General'; import Categories from './Categories'; +import { MoneyMoneyRes } from '../../moneymoney'; type Props = { state: BudgetState; dispatch: Dispatch; + moneyMoney: MoneyMoneyRes; + onClose: () => void; }; export default function Settings(props: Props) { - const showSettings = useSetShowSettings(); const [tab, setTab] = useState<'general' | 'categories'>('general'); + const { accounts } = props.state.settings; const valid = accounts.length > 0; @@ -22,11 +24,7 @@ export default function Settings(props: Props) { background header={

- Settings