mirror of
https://github.com/pybricks/pybricks-code.git
synced 2026-09-12 01:23:52 +00:00
use typed-redux-saga in flash-firmware sagas
This make things a bit more type safe and a bit easier to read.
This commit is contained in:
@@ -29,6 +29,7 @@
|
||||
"@types/web-bluetooth": "^0.0.9",
|
||||
"@types/zen-push": "^0.1.1",
|
||||
"ace-builds": "^1.4.12",
|
||||
"babel-plugin-macros": "^3.0.1",
|
||||
"file-saver": "^2.0.5",
|
||||
"jszip": "^3.5.0",
|
||||
"license-webpack-plugin": "^2.3.11",
|
||||
@@ -45,6 +46,7 @@
|
||||
"redux-logger": "^3.0.6",
|
||||
"redux-saga": "^1.1.3",
|
||||
"spdx-satisfies": "^5.0.0",
|
||||
"typed-redux-saga": "^1.3.1",
|
||||
"typescript": "~4.1.3",
|
||||
"web-vitals": "^1.0.1",
|
||||
"xterm": "^4.9.0",
|
||||
@@ -80,9 +82,12 @@
|
||||
"@typescript-eslint/parser": "^4.13.0",
|
||||
"eslint": "^7.17.0",
|
||||
"eslint-config-prettier": "^7.1.0",
|
||||
"eslint-config-typed-fp": "^1.3.0",
|
||||
"eslint-plugin-functional": "^3.2.1",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-prettier": "^3.3.1",
|
||||
"eslint-plugin-react": "^7.22.0",
|
||||
"eslint-plugin-total-functions": "^4.7.2",
|
||||
"jest-mock-extended": "^1.0.9",
|
||||
"prettier": "^2.2.1"
|
||||
}
|
||||
|
||||
+115
-135
@@ -1,19 +1,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2021 The Pybricks Authors
|
||||
|
||||
import {
|
||||
FirmwareMetadata,
|
||||
FirmwareReader,
|
||||
FirmwareReaderError,
|
||||
HubType,
|
||||
} from '@pybricks/firmware';
|
||||
import { FirmwareReader, FirmwareReaderError, HubType } from '@pybricks/firmware';
|
||||
import cityHubZip from '@pybricks/firmware/build/cityhub.zip';
|
||||
import moveHubZip from '@pybricks/firmware/build/movehub.zip';
|
||||
import technicHubZip from '@pybricks/firmware/build/technichub.zip';
|
||||
import { Ace } from 'ace-builds';
|
||||
import {
|
||||
Effect,
|
||||
StrictEffect,
|
||||
SagaGenerator,
|
||||
all,
|
||||
call,
|
||||
cancel,
|
||||
@@ -24,7 +17,7 @@ import {
|
||||
select,
|
||||
take,
|
||||
takeEvery,
|
||||
} from 'redux-saga/effects';
|
||||
} from 'typed-redux-saga/macro';
|
||||
import { Action } from '../actions';
|
||||
import {
|
||||
FailToStartReasonType,
|
||||
@@ -36,24 +29,17 @@ import {
|
||||
didStart,
|
||||
} from '../actions/flash-firmware';
|
||||
import {
|
||||
BootloaderChecksumRequestAction,
|
||||
BootloaderChecksumResponseAction,
|
||||
BootloaderConnectionActionType,
|
||||
BootloaderConnectionDidConnectAction,
|
||||
BootloaderConnectionDidFailToConnectAction,
|
||||
BootloaderDidRequestAction,
|
||||
BootloaderDidRequestType,
|
||||
BootloaderDisconnectRequestAction,
|
||||
BootloaderEraseRequestAction,
|
||||
BootloaderEraseResponseAction,
|
||||
BootloaderErrorResponseAction,
|
||||
BootloaderInfoRequestAction,
|
||||
BootloaderInfoResponseAction,
|
||||
BootloaderInitRequestAction,
|
||||
BootloaderInitResponseAction,
|
||||
BootloaderProgramRequestAction,
|
||||
BootloaderProgramResponseAction,
|
||||
BootloaderRebootRequestAction,
|
||||
BootloaderResponseAction,
|
||||
BootloaderResponseActionType,
|
||||
checksumRequest,
|
||||
@@ -74,7 +60,7 @@ import {
|
||||
import * as notification from '../actions/notification';
|
||||
import { MaxProgramFlashSize } from '../protocols/lwp3-bootloader';
|
||||
import { RootState } from '../reducers';
|
||||
import { Maybe, maybe } from '../utils';
|
||||
import { defined, maybe } from '../utils';
|
||||
import { fmod, sumComplement32 } from '../utils/math';
|
||||
|
||||
const firmwareZipMap = new Map<HubType, string>([
|
||||
@@ -83,25 +69,10 @@ const firmwareZipMap = new Map<HubType, string>([
|
||||
[HubType.MoveHub, moveHubZip],
|
||||
]);
|
||||
|
||||
/**
|
||||
* Helper type for return value of wait() function.
|
||||
*/
|
||||
type WaitResponse<T extends BootloaderResponseAction> = [
|
||||
T,
|
||||
BootloaderErrorResponseAction,
|
||||
boolean,
|
||||
];
|
||||
|
||||
function* waitForDidRequest(id: number): Generator {
|
||||
const didRequest = (yield take(
|
||||
(a: Action) =>
|
||||
a.type === BootloaderDidRequestType &&
|
||||
(a as BootloaderDidRequestAction).id === id,
|
||||
)) as BootloaderDidRequestAction;
|
||||
if (didRequest.err) {
|
||||
console.error(didRequest.err);
|
||||
}
|
||||
return didRequest;
|
||||
function* waitForDidRequest(id: number): SagaGenerator<BootloaderDidRequestAction> {
|
||||
return yield* take<BootloaderDidRequestAction>(
|
||||
(a: Action) => a.type === BootloaderDidRequestType && a.id === id,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,8 +81,19 @@ function* waitForDidRequest(id: number): Generator {
|
||||
* @param type The action type to wait for.
|
||||
* @param timeout The timeout in milliseconds.
|
||||
*/
|
||||
function waitForResponse(type: BootloaderResponseActionType, timeout = 500): Effect {
|
||||
return race([take(type), take(BootloaderResponseActionType.Error), delay(timeout)]);
|
||||
function* waitForResponse<T extends BootloaderResponseAction>(
|
||||
type: BootloaderResponseActionType,
|
||||
timeout = 500,
|
||||
): SagaGenerator<{
|
||||
response?: T;
|
||||
error?: BootloaderErrorResponseAction;
|
||||
timeout?: boolean;
|
||||
}> {
|
||||
return yield* race({
|
||||
response: take<T>(type),
|
||||
error: take<BootloaderErrorResponseAction>(BootloaderResponseActionType.Error),
|
||||
timeout: delay(timeout),
|
||||
});
|
||||
}
|
||||
|
||||
function* firmwareIterator(data: DataView, maxSize: number): Generator<number> {
|
||||
@@ -133,27 +115,27 @@ function* firmwareIterator(data: DataView, maxSize: number): Generator<number> {
|
||||
function* loadFirmware(
|
||||
data: ArrayBuffer,
|
||||
program: string | undefined,
|
||||
): Generator<StrictEffect, { firmware: Uint8Array; deviceId: HubType }> {
|
||||
const reader = (yield call(() =>
|
||||
maybe(FirmwareReader.load(data)),
|
||||
)) as Maybe<FirmwareReader>;
|
||||
): SagaGenerator<{ firmware: Uint8Array; deviceId: HubType }> {
|
||||
const [reader, readerErr] = yield* call(() => maybe(FirmwareReader.load(data)));
|
||||
|
||||
if (reader instanceof Error) {
|
||||
if (reader instanceof FirmwareReaderError) {
|
||||
yield put(didFailToStart(FailToStartReasonType.ZipError, reader));
|
||||
if (readerErr) {
|
||||
// istanbul ignore else: unexpected error
|
||||
if (readerErr instanceof FirmwareReaderError) {
|
||||
yield* put(didFailToStart(FailToStartReasonType.ZipError, readerErr));
|
||||
} else {
|
||||
yield put(didFailToStart(FailToStartReasonType.Unknown, reader));
|
||||
yield* put(didFailToStart(FailToStartReasonType.Unknown, readerErr));
|
||||
}
|
||||
yield cancel();
|
||||
throw 'not reached';
|
||||
yield* cancel();
|
||||
}
|
||||
|
||||
const firmwareBase = (yield call(() => reader.readFirmwareBase())) as Uint8Array;
|
||||
const metadata = (yield call(() => reader.readMetadata())) as FirmwareMetadata;
|
||||
defined(reader);
|
||||
|
||||
const firmwareBase = yield* call(() => reader.readFirmwareBase());
|
||||
const metadata = yield* call(() => reader.readMetadata());
|
||||
|
||||
// if a user program was not given, then use main.py from the frimware.zip
|
||||
if (program === undefined) {
|
||||
program = (yield call(() => reader.readMainPy())) as string;
|
||||
program = yield* call(() => reader.readMainPy());
|
||||
}
|
||||
|
||||
if (metadata['mpy-abi-version'] !== 5) {
|
||||
@@ -162,16 +144,18 @@ function* loadFirmware(
|
||||
);
|
||||
}
|
||||
|
||||
yield put(compile(program, metadata['mpy-cross-options']));
|
||||
const [mpy, mpyFail] = (yield race([
|
||||
take(MpyActionType.DidCompile),
|
||||
take(MpyActionType.DidFailToCompile),
|
||||
])) as [MpyDidCompileAction, MpyDidFailToCompileAction];
|
||||
yield* put(compile(program, metadata['mpy-cross-options']));
|
||||
const { mpy, mpyFail } = yield* race({
|
||||
mpy: take<MpyDidCompileAction>(MpyActionType.DidCompile),
|
||||
mpyFail: take<MpyDidFailToCompileAction>(MpyActionType.DidFailToCompile),
|
||||
});
|
||||
|
||||
if (mpyFail) {
|
||||
throw Error(mpyFail.err.join('\n'));
|
||||
}
|
||||
|
||||
defined(mpy);
|
||||
|
||||
// compute offset for checksum - must be aligned to 4-byte boundary
|
||||
const checksumOffset =
|
||||
metadata['user-mpy-offset'] + 4 + mpy.data.length + fmod(-mpy.data.length, 4);
|
||||
@@ -210,14 +194,12 @@ function* flashFirmware(action: FlashFirmwareFlashAction): Generator {
|
||||
|
||||
let program: string | undefined = undefined;
|
||||
|
||||
const flashCurrentProgram = (yield select(
|
||||
const flashCurrentProgram = yield* select(
|
||||
(s: RootState) => s.settings.flashCurrentProgram,
|
||||
)) as boolean;
|
||||
);
|
||||
|
||||
if (flashCurrentProgram) {
|
||||
const editor = (yield select(
|
||||
(s: RootState) => s.editor.current,
|
||||
)) as Ace.EditSession | null;
|
||||
const editor = yield* select((s: RootState) => s.editor.current);
|
||||
|
||||
// istanbul ignore if: it is a bug to dispatch this action with no current editor
|
||||
if (editor === null) {
|
||||
@@ -232,109 +214,111 @@ function* flashFirmware(action: FlashFirmwareFlashAction): Generator {
|
||||
({ firmware, deviceId } = yield* loadFirmware(action.data, program));
|
||||
}
|
||||
|
||||
yield put(connect());
|
||||
const connectResult = (yield take([
|
||||
yield* put(connect());
|
||||
const connectResult = yield* take<
|
||||
| BootloaderConnectionDidConnectAction
|
||||
| BootloaderConnectionDidFailToConnectAction
|
||||
>([
|
||||
BootloaderConnectionActionType.DidConnect,
|
||||
BootloaderConnectionActionType.DidFailToConnect,
|
||||
])) as
|
||||
| BootloaderConnectionDidConnectAction
|
||||
| BootloaderConnectionDidFailToConnectAction;
|
||||
]);
|
||||
|
||||
if (connectResult.type === BootloaderConnectionActionType.DidFailToConnect) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextMessageId = (yield getContext('nextMessageId')) as () => number;
|
||||
const nextMessageId = yield* getContext<() => number>('nextMessageId');
|
||||
|
||||
const infoAction = (yield put(
|
||||
infoRequest(nextMessageId()),
|
||||
)) as BootloaderInfoRequestAction;
|
||||
const [, info] = (yield all([
|
||||
waitForDidRequest(infoAction.id),
|
||||
waitForResponse(BootloaderResponseActionType.Info),
|
||||
])) as [BootloaderDidRequestAction, WaitResponse<BootloaderInfoResponseAction>];
|
||||
if (!info[0]) {
|
||||
const infoAction = yield* put(infoRequest(nextMessageId()));
|
||||
const { info } = yield* all({
|
||||
sent: waitForDidRequest(infoAction.id),
|
||||
info: waitForResponse<BootloaderInfoResponseAction>(
|
||||
BootloaderResponseActionType.Info,
|
||||
),
|
||||
});
|
||||
if (!info.response) {
|
||||
throw Error(`failed to get info: ${info}`);
|
||||
}
|
||||
|
||||
if (deviceId !== undefined && info[0].hubType !== deviceId) {
|
||||
throw Error(`Connected to ${info[0].hubType} but firmware is for ${deviceId}`);
|
||||
if (deviceId !== undefined && info.response.hubType !== deviceId) {
|
||||
throw Error(
|
||||
`Connected to ${info.response.hubType} but firmware is for ${deviceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (firmware === undefined) {
|
||||
const firmwarePath = firmwareZipMap.get(info[0].hubType);
|
||||
const firmwarePath = firmwareZipMap.get(info.response.hubType);
|
||||
if (firmwarePath === undefined) {
|
||||
yield put(
|
||||
yield* put(
|
||||
notification.add(
|
||||
'error',
|
||||
"Sorry, we don't have firmware for this hub yet.",
|
||||
),
|
||||
);
|
||||
yield put(disconnectRequest(nextMessageId()));
|
||||
yield* put(disconnectRequest(nextMessageId()));
|
||||
return;
|
||||
}
|
||||
|
||||
const response = (yield call(() => fetch(firmwarePath))) as Response;
|
||||
const response = yield* call(() => fetch(firmwarePath));
|
||||
if (!response.ok) {
|
||||
yield put(notification.add('error', 'Failed to fetch firmware.'));
|
||||
const disconnectAction = (yield put(
|
||||
disconnectRequest(nextMessageId()),
|
||||
)) as BootloaderDisconnectRequestAction;
|
||||
yield waitForDidRequest(disconnectAction.id);
|
||||
yield* put(notification.add('error', 'Failed to fetch firmware.'));
|
||||
const disconnectAction = yield* put(disconnectRequest(nextMessageId()));
|
||||
yield* waitForDidRequest(disconnectAction.id);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = (yield call(() => response.arrayBuffer())) as ArrayBuffer;
|
||||
const data = yield* call(() => response.arrayBuffer());
|
||||
({ firmware, deviceId } = yield* loadFirmware(data, program));
|
||||
|
||||
if (deviceId !== undefined && info[0].hubType !== deviceId) {
|
||||
if (deviceId !== undefined && info.response.hubType !== deviceId) {
|
||||
throw Error(
|
||||
`Connected to ${info[0].hubType} but firmware is for ${deviceId}`,
|
||||
`Connected to ${info.response.hubType} but firmware is for ${deviceId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
yield put(didStart());
|
||||
yield* put(didStart());
|
||||
|
||||
const eraseAction = (yield put(
|
||||
eraseRequest(nextMessageId()),
|
||||
)) as BootloaderEraseRequestAction;
|
||||
const [, erase] = (yield all([
|
||||
waitForDidRequest(eraseAction.id),
|
||||
waitForResponse(BootloaderResponseActionType.Erase, 5000),
|
||||
])) as [BootloaderDidRequestAction, WaitResponse<BootloaderEraseResponseAction>];
|
||||
if (!erase[0] || erase[0].result) {
|
||||
const eraseAction = yield* put(eraseRequest(nextMessageId()));
|
||||
const { erase } = yield* all({
|
||||
sent: waitForDidRequest(eraseAction.id),
|
||||
erase: waitForResponse<BootloaderEraseResponseAction>(
|
||||
BootloaderResponseActionType.Erase,
|
||||
5000,
|
||||
),
|
||||
});
|
||||
if (!erase.response || erase.response.result) {
|
||||
// TODO: proper error handling
|
||||
throw Error(`Failed to erase: ${erase}`);
|
||||
}
|
||||
|
||||
const initAction = (yield put(
|
||||
initRequest(nextMessageId(), firmware.length),
|
||||
)) as BootloaderInitRequestAction;
|
||||
const [, init] = (yield all([
|
||||
waitForDidRequest(initAction.id),
|
||||
waitForResponse(BootloaderResponseActionType.Init),
|
||||
])) as [BootloaderDidRequestAction, WaitResponse<BootloaderInitResponseAction>];
|
||||
if (!init[0] || init[0].result) {
|
||||
const initAction = yield* put(initRequest(nextMessageId(), firmware.length));
|
||||
const { init } = yield* all({
|
||||
sent: waitForDidRequest(initAction.id),
|
||||
init: waitForResponse<BootloaderInitResponseAction>(
|
||||
BootloaderResponseActionType.Init,
|
||||
),
|
||||
});
|
||||
if (!init.response || init.response.result) {
|
||||
// TODO: proper error handling
|
||||
throw Error(`Failed to init: ${init}`);
|
||||
}
|
||||
|
||||
// 14 is "safe" size for all hubs
|
||||
const maxDataSize = MaxProgramFlashSize.get(info[0].hubType) || 14;
|
||||
const maxDataSize = MaxProgramFlashSize.get(info.response.hubType) || 14;
|
||||
|
||||
for (let count = 1, offset = 0; ; count++) {
|
||||
const payload = firmware.slice(offset, offset + maxDataSize);
|
||||
const programAction = (yield put(
|
||||
const programAction = yield* put(
|
||||
programRequest(
|
||||
nextMessageId(),
|
||||
info[0].startAddress + offset,
|
||||
info.response.startAddress + offset,
|
||||
payload.buffer,
|
||||
),
|
||||
)) as BootloaderProgramRequestAction;
|
||||
yield waitForDidRequest(programAction.id);
|
||||
);
|
||||
yield* waitForDidRequest(programAction.id);
|
||||
|
||||
yield put(didProgress(offset / firmware.length));
|
||||
yield* put(didProgress(offset / firmware.length));
|
||||
|
||||
// we don't want to request checksum if this is the last packet since
|
||||
// the bootloader will send a response to the program request already.
|
||||
@@ -348,46 +332,42 @@ function* flashFirmware(action: FlashFirmwareFlashAction): Generator {
|
||||
// number of packets that can be queued in the Bluetooth chip on
|
||||
// the hub is not known and could vary by device.
|
||||
if (count % 10 === 0) {
|
||||
const checksumAction = (yield put(
|
||||
checksumRequest(nextMessageId()),
|
||||
)) as BootloaderChecksumRequestAction;
|
||||
const [, checksum] = (yield all([
|
||||
waitForDidRequest(checksumAction.id),
|
||||
waitForResponse(BootloaderResponseActionType.Checksum, 5000),
|
||||
])) as [
|
||||
BootloaderDidRequestAction,
|
||||
WaitResponse<BootloaderChecksumResponseAction>,
|
||||
];
|
||||
if (!checksum[0]) {
|
||||
const checksumAction = yield* put(checksumRequest(nextMessageId()));
|
||||
const { checksum } = yield* all({
|
||||
sent: waitForDidRequest(checksumAction.id),
|
||||
checksum: waitForResponse<BootloaderChecksumResponseAction>(
|
||||
BootloaderResponseActionType.Checksum,
|
||||
5000,
|
||||
),
|
||||
});
|
||||
if (!checksum.response) {
|
||||
// TODO: proper error handling
|
||||
throw Error(`Failed to get checksum: ${checksum}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const flash = (yield waitForResponse(
|
||||
const flash = yield* waitForResponse<BootloaderProgramResponseAction>(
|
||||
BootloaderResponseActionType.Program,
|
||||
5000,
|
||||
)) as WaitResponse<BootloaderProgramResponseAction>;
|
||||
if (!flash[0]) {
|
||||
);
|
||||
if (!flash.response) {
|
||||
throw Error(`failed to get final response: ${flash}`);
|
||||
}
|
||||
if (flash[0].count !== firmware.length) {
|
||||
if (flash.response.count !== firmware.length) {
|
||||
// TODO: proper error handling
|
||||
throw Error("Didn't flash all bytes");
|
||||
}
|
||||
|
||||
yield put(didProgress(1));
|
||||
yield* put(didProgress(1));
|
||||
|
||||
// this will cause the remote device to disconnect and reboot
|
||||
const rebootAction = (yield put(
|
||||
rebootRequest(nextMessageId()),
|
||||
)) as BootloaderRebootRequestAction;
|
||||
yield waitForDidRequest(rebootAction.id);
|
||||
const rebootAction = yield* put(rebootRequest(nextMessageId()));
|
||||
yield* waitForDidRequest(rebootAction.id);
|
||||
|
||||
yield put(didFinish());
|
||||
yield* put(didFinish());
|
||||
}
|
||||
|
||||
export default function* (): Generator {
|
||||
yield takeEvery(FlashFirmwareActionType.FlashFirmware, flashFirmware);
|
||||
yield* takeEvery(FlashFirmwareActionType.FlashFirmware, flashFirmware);
|
||||
}
|
||||
|
||||
+11
-4
@@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020 The Pybricks Authors
|
||||
|
||||
import { assert, hex, maybe } from '.';
|
||||
import { assert, defined, hex, maybe } from '.';
|
||||
|
||||
test('assert', () => {
|
||||
const assertTrue = jest.fn(() => assert(true, 'should not throw'));
|
||||
@@ -11,14 +11,21 @@ test('assert', () => {
|
||||
expect(() => assert(false, 'should throw')).toThrow();
|
||||
});
|
||||
|
||||
describe('defined', () => {
|
||||
expect(() => defined('test')).not.toThrow();
|
||||
expect(() => defined(undefined)).toThrowError();
|
||||
});
|
||||
|
||||
describe('maybe', () => {
|
||||
test('resolved', async () => {
|
||||
const result = await maybe(Promise.resolve('test'));
|
||||
const [result, error] = await maybe(Promise.resolve('test'));
|
||||
expect(result).toBe('test');
|
||||
expect(error).toBeUndefined();
|
||||
});
|
||||
test('rejected', async () => {
|
||||
const result = await maybe(Promise.reject(new Error('test')));
|
||||
expect(result).toBeInstanceOf(Error);
|
||||
const [result, error] = await maybe(Promise.reject(new Error('test')));
|
||||
expect(result).toBeUndefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+13
-4
@@ -7,20 +7,29 @@
|
||||
* @param condition A condition that is assumed to be true
|
||||
* @param message Informational message for debugging
|
||||
*/
|
||||
export function assert(condition: boolean, message: string): void {
|
||||
export function assert(condition: boolean, message: string): asserts condition {
|
||||
if (!condition) {
|
||||
throw Error(message);
|
||||
}
|
||||
}
|
||||
|
||||
export type Maybe<T> = T | Error;
|
||||
/**
|
||||
* Asserts that an object is not undefined. This is used to make the type
|
||||
* checker happy with `maybe()` and saga `race()` and `all()` effects where
|
||||
* we have the condition "if A is undefined, then B is not undefined".
|
||||
*/
|
||||
export function defined<T>(obj: T): asserts obj is NonNullable<T> {
|
||||
assert(obj !== undefined, 'undefined object');
|
||||
}
|
||||
|
||||
export type Maybe<T> = [T?, Error?];
|
||||
|
||||
/** Wraps a promise in try/catch and returns the promise result or error. */
|
||||
export async function maybe<T>(promise: Promise<T>): Promise<Maybe<T>> {
|
||||
try {
|
||||
return await promise;
|
||||
return [await promise];
|
||||
} catch (err) {
|
||||
return err;
|
||||
return [undefined, err];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2138,6 +2138,20 @@
|
||||
semver "^7.3.2"
|
||||
tsutils "^3.17.1"
|
||||
|
||||
"@typescript-eslint/eslint-plugin@^4.8.2":
|
||||
version "4.14.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.14.0.tgz#92db8e7c357ed7d69632d6843ca70b71be3a721d"
|
||||
integrity sha512-IJ5e2W7uFNfg4qh9eHkHRUCbgZ8VKtGwD07kannJvM5t/GU8P8+24NX8gi3Hf5jST5oWPY8kyV1s/WtfiZ4+Ww==
|
||||
dependencies:
|
||||
"@typescript-eslint/experimental-utils" "4.14.0"
|
||||
"@typescript-eslint/scope-manager" "4.14.0"
|
||||
debug "^4.1.1"
|
||||
functional-red-black-tree "^1.0.1"
|
||||
lodash "^4.17.15"
|
||||
regexpp "^3.0.0"
|
||||
semver "^7.3.2"
|
||||
tsutils "^3.17.1"
|
||||
|
||||
"@typescript-eslint/experimental-utils@4.13.0", "@typescript-eslint/experimental-utils@^4.0.1":
|
||||
version "4.13.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.13.0.tgz#9dc9ab375d65603b43d938a0786190a0c72be44e"
|
||||
@@ -2150,6 +2164,18 @@
|
||||
eslint-scope "^5.0.0"
|
||||
eslint-utils "^2.0.0"
|
||||
|
||||
"@typescript-eslint/experimental-utils@4.14.0", "@typescript-eslint/experimental-utils@^4.8.2", "@typescript-eslint/experimental-utils@^4.9.1":
|
||||
version "4.14.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.14.0.tgz#5aa7b006736634f588a69ee343ca959cd09988df"
|
||||
integrity sha512-6i6eAoiPlXMKRbXzvoQD5Yn9L7k9ezzGRvzC/x1V3650rUk3c3AOjQyGYyF9BDxQQDK2ElmKOZRD0CbtdkMzQQ==
|
||||
dependencies:
|
||||
"@types/json-schema" "^7.0.3"
|
||||
"@typescript-eslint/scope-manager" "4.14.0"
|
||||
"@typescript-eslint/types" "4.14.0"
|
||||
"@typescript-eslint/typescript-estree" "4.14.0"
|
||||
eslint-scope "^5.0.0"
|
||||
eslint-utils "^2.0.0"
|
||||
|
||||
"@typescript-eslint/experimental-utils@^3.10.1":
|
||||
version "3.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.10.1.tgz#e179ffc81a80ebcae2ea04e0332f8b251345a686"
|
||||
@@ -2171,6 +2197,16 @@
|
||||
"@typescript-eslint/typescript-estree" "4.13.0"
|
||||
debug "^4.1.1"
|
||||
|
||||
"@typescript-eslint/parser@^4.8.2":
|
||||
version "4.14.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.14.0.tgz#62d4cd2079d5c06683e9bfb200c758f292c4dee7"
|
||||
integrity sha512-sUDeuCjBU+ZF3Lzw0hphTyScmDDJ5QVkyE21pRoBo8iDl7WBtVFS+WDN3blY1CH3SBt7EmYCw6wfmJjF0l/uYg==
|
||||
dependencies:
|
||||
"@typescript-eslint/scope-manager" "4.14.0"
|
||||
"@typescript-eslint/types" "4.14.0"
|
||||
"@typescript-eslint/typescript-estree" "4.14.0"
|
||||
debug "^4.1.1"
|
||||
|
||||
"@typescript-eslint/scope-manager@4.13.0":
|
||||
version "4.13.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.13.0.tgz#5b45912a9aa26b29603d8fa28f5e09088b947141"
|
||||
@@ -2179,6 +2215,14 @@
|
||||
"@typescript-eslint/types" "4.13.0"
|
||||
"@typescript-eslint/visitor-keys" "4.13.0"
|
||||
|
||||
"@typescript-eslint/scope-manager@4.14.0":
|
||||
version "4.14.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.14.0.tgz#55a4743095d684e1f7b7180c4bac2a0a3727f517"
|
||||
integrity sha512-/J+LlRMdbPh4RdL4hfP1eCwHN5bAhFAGOTsvE6SxsrM/47XQiPSgF5MDgLyp/i9kbZV9Lx80DW0OpPkzL+uf8Q==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "4.14.0"
|
||||
"@typescript-eslint/visitor-keys" "4.14.0"
|
||||
|
||||
"@typescript-eslint/types@3.10.1":
|
||||
version "3.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.10.1.tgz#1d7463fa7c32d8a23ab508a803ca2fe26e758727"
|
||||
@@ -2189,6 +2233,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.13.0.tgz#6a7c6015a59a08fbd70daa8c83dfff86250502f8"
|
||||
integrity sha512-/+aPaq163oX+ObOG00M0t9tKkOgdv9lq0IQv/y4SqGkAXmhFmCfgsELV7kOCTb2vVU5VOmVwXBXJTDr353C1rQ==
|
||||
|
||||
"@typescript-eslint/types@4.14.0":
|
||||
version "4.14.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.14.0.tgz#d8a8202d9b58831d6fd9cee2ba12f8a5a5dd44b6"
|
||||
integrity sha512-VsQE4VvpldHrTFuVPY1ZnHn/Txw6cZGjL48e+iBxTi2ksa9DmebKjAeFmTVAYoSkTk7gjA7UqJ7pIsyifTsI4A==
|
||||
|
||||
"@typescript-eslint/typescript-estree@3.10.1":
|
||||
version "3.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz#fd0061cc38add4fad45136d654408569f365b853"
|
||||
@@ -2217,6 +2266,20 @@
|
||||
semver "^7.3.2"
|
||||
tsutils "^3.17.1"
|
||||
|
||||
"@typescript-eslint/typescript-estree@4.14.0":
|
||||
version "4.14.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.14.0.tgz#4bcd67486e9acafc3d0c982b23a9ab8ac8911ed7"
|
||||
integrity sha512-wRjZ5qLao+bvS2F7pX4qi2oLcOONIB+ru8RGBieDptq/SudYwshveORwCVU4/yMAd4GK7Fsf8Uq1tjV838erag==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "4.14.0"
|
||||
"@typescript-eslint/visitor-keys" "4.14.0"
|
||||
debug "^4.1.1"
|
||||
globby "^11.0.1"
|
||||
is-glob "^4.0.1"
|
||||
lodash "^4.17.15"
|
||||
semver "^7.3.2"
|
||||
tsutils "^3.17.1"
|
||||
|
||||
"@typescript-eslint/visitor-keys@3.10.1":
|
||||
version "3.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz#cd4274773e3eb63b2e870ac602274487ecd1e931"
|
||||
@@ -2232,6 +2295,14 @@
|
||||
"@typescript-eslint/types" "4.13.0"
|
||||
eslint-visitor-keys "^2.0.0"
|
||||
|
||||
"@typescript-eslint/visitor-keys@4.14.0":
|
||||
version "4.14.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.14.0.tgz#b1090d9d2955b044b2ea2904a22496849acbdf54"
|
||||
integrity sha512-MeHHzUyRI50DuiPgV9+LxcM52FCJFYjJiWHtXlbyC27b80mfOwKeiKI+MHOTEpcpfmoPFm/vvQS88bYIx6PZTA==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "4.14.0"
|
||||
eslint-visitor-keys "^2.0.0"
|
||||
|
||||
"@webassemblyjs/ast@1.9.0":
|
||||
version "1.9.0"
|
||||
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964"
|
||||
@@ -2681,7 +2752,7 @@ array.prototype.flat@^1.2.3:
|
||||
define-properties "^1.1.3"
|
||||
es-abstract "^1.18.0-next.1"
|
||||
|
||||
array.prototype.flatmap@^1.2.3:
|
||||
array.prototype.flatmap@^1.2.3, array.prototype.flatmap@^1.2.4:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.2.4.tgz#94cfd47cc1556ec0747d97f7c7738c58122004c9"
|
||||
integrity sha512-r9Z0zYoxqHz60vvQbWEdXIEtCwHF0yxaWfno9qzXeNHvfyl3BZqygmGzb84dsubyaXLH4husF+NFgMSdpZhk2Q==
|
||||
@@ -2893,7 +2964,7 @@ babel-plugin-jest-hoist@^26.6.2:
|
||||
"@types/babel__core" "^7.0.0"
|
||||
"@types/babel__traverse" "^7.0.6"
|
||||
|
||||
babel-plugin-macros@2.8.0:
|
||||
babel-plugin-macros@2.8.0, babel-plugin-macros@^2.8.0:
|
||||
version "2.8.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz#0f958a7cc6556b1e65344465d99111a1e5e10138"
|
||||
integrity sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==
|
||||
@@ -2902,6 +2973,15 @@ babel-plugin-macros@2.8.0:
|
||||
cosmiconfig "^6.0.0"
|
||||
resolve "^1.12.0"
|
||||
|
||||
babel-plugin-macros@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.0.1.tgz#0d412d68f5b3d1b64358f24ab099bd148724e2a9"
|
||||
integrity sha512-CKt4+Oy9k2wiN+hT1uZzOw7d8zb1anbQpf7KLwaaXRCi/4pzKdFKHf7v5mvoPmjkmxshh7eKZQuRop06r5WP4w==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.12.5"
|
||||
cosmiconfig "^7.0.0"
|
||||
resolve "^1.19.0"
|
||||
|
||||
babel-plugin-named-asset-import@^0.3.7:
|
||||
version "0.3.7"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.7.tgz#156cd55d3f1228a5765774340937afc8398067dd"
|
||||
@@ -4861,6 +4941,11 @@ escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
|
||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
|
||||
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
|
||||
|
||||
escape-string-regexp@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
|
||||
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
|
||||
|
||||
escodegen@^1.14.1:
|
||||
version "1.14.3"
|
||||
resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503"
|
||||
@@ -4885,6 +4970,11 @@ eslint-config-react-app@^6.0.0:
|
||||
dependencies:
|
||||
confusing-browser-globals "^1.0.10"
|
||||
|
||||
eslint-config-typed-fp@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-config-typed-fp/-/eslint-config-typed-fp-1.3.0.tgz#ca62050793a80c0b9af6f370e925797fa4c243f9"
|
||||
integrity sha512-I6+/szKXAbZQ23pCjVAoqaM0AtYXIfo40QrLKHFfZ/Fh+ROnd2vOawCJzgYfIuPZrjbVbnlsLuM8LTK19YihYA==
|
||||
|
||||
eslint-import-resolver-node@^0.3.4:
|
||||
version "0.3.4"
|
||||
resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz#85ffa81942c25012d8231096ddf679c03042c717"
|
||||
@@ -4909,6 +4999,17 @@ eslint-plugin-flowtype@^5.2.0:
|
||||
lodash "^4.17.15"
|
||||
string-natural-compare "^3.0.1"
|
||||
|
||||
eslint-plugin-functional@^3.2.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-functional/-/eslint-plugin-functional-3.2.1.tgz#d5ad668b57646ad24f4ef0476328408681d59061"
|
||||
integrity sha512-uJ8W0FznWsKp4exxO79b0xSc1WNROzDiVNGgSFOwdZCBeUHQf89BqwqlshNW9aSz/kg2gVGs+Ue6AeTpNSFM/g==
|
||||
dependencies:
|
||||
"@typescript-eslint/experimental-utils" "^4.9.1"
|
||||
array.prototype.flatmap "^1.2.4"
|
||||
deepmerge "^4.2.2"
|
||||
escape-string-regexp "^4.0.0"
|
||||
object.fromentries "^2.0.3"
|
||||
|
||||
eslint-plugin-import@^2.22.1:
|
||||
version "2.22.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.22.1.tgz#0896c7e6a0cf44109a2d97b95903c2bb689d7702"
|
||||
@@ -4988,6 +5089,16 @@ eslint-plugin-testing-library@^3.9.2:
|
||||
dependencies:
|
||||
"@typescript-eslint/experimental-utils" "^3.10.1"
|
||||
|
||||
eslint-plugin-total-functions@^4.7.2:
|
||||
version "4.7.2"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-total-functions/-/eslint-plugin-total-functions-4.7.2.tgz#e60801da31e1f0e30a2d28b42921ab1f72cb66de"
|
||||
integrity sha512-NG0Is/W+l9vGMbo6wABGGw00Wl6VR0JdN99u28bTKfRzisxnRFJGR/i12jFgQqJHxPxGxC+lLxZ/E5NtwOjo+A==
|
||||
dependencies:
|
||||
"@typescript-eslint/eslint-plugin" "^4.8.2"
|
||||
"@typescript-eslint/experimental-utils" "^4.8.2"
|
||||
"@typescript-eslint/parser" "^4.8.2"
|
||||
tsutils "^3.17.1"
|
||||
|
||||
eslint-scope@^4.0.3:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848"
|
||||
@@ -8386,7 +8497,7 @@ object.entries@^1.1.0, object.entries@^1.1.2:
|
||||
es-abstract "^1.18.0-next.1"
|
||||
has "^1.0.3"
|
||||
|
||||
object.fromentries@^2.0.2:
|
||||
object.fromentries@^2.0.2, object.fromentries@^2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.3.tgz#13cefcffa702dc67750314a3305e8cb3fad1d072"
|
||||
integrity sha512-IDUSMXs6LOSJBWE++L0lzIbSqHl9KDCfff2x/JSEIDtEUavUnyMYC2ZGay/04Zq4UT8lvd4xNhU4/YHKibAOlw==
|
||||
@@ -10417,7 +10528,7 @@ resolve@1.18.1:
|
||||
is-core-module "^2.0.0"
|
||||
path-parse "^1.0.6"
|
||||
|
||||
resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.18.1, resolve@^1.3.2, resolve@^1.8.1:
|
||||
resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.18.1, resolve@^1.19.0, resolve@^1.3.2, resolve@^1.8.1:
|
||||
version "1.19.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c"
|
||||
integrity sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==
|
||||
@@ -11855,6 +11966,14 @@ type@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/type/-/type-2.1.0.tgz#9bdc22c648cf8cf86dd23d32336a41cfb6475e3f"
|
||||
integrity sha512-G9absDWvhAWCV2gmF1zKud3OyC61nZDwWvBL2DApaVFogI07CprggiQAOOjvp2NRjYWFzPyu7vwtDrQFq8jeSA==
|
||||
|
||||
typed-redux-saga@^1.3.1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/typed-redux-saga/-/typed-redux-saga-1.3.1.tgz#92b01db41e3510102f87eb9ff261ec73d38a2e44"
|
||||
integrity sha512-nUj1/1/SAesEsZrr7o24ID+++CqZ6QfPVDcwhY2rVmm4vEBr/vbDHJ6j/w6SomOcooLwnh3sdaWVhNEIy7VgNA==
|
||||
optionalDependencies:
|
||||
"@babel/helper-module-imports" "^7.12.1"
|
||||
babel-plugin-macros "^2.8.0"
|
||||
|
||||
typed-styles@^0.0.7:
|
||||
version "0.0.7"
|
||||
resolved "https://registry.yarnpkg.com/typed-styles/-/typed-styles-0.0.7.tgz#93392a008794c4595119ff62dde6809dbc40a3d9"
|
||||
|
||||
Reference in New Issue
Block a user