From fb3f70654e6a734a6793e06c4d06237464aaa024 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Sun, 17 Mar 2019 19:40:44 -0700 Subject: [PATCH 01/23] Start moving emitter to own package. --- packages/core/src/Console.ts | 23 ++++++++++- packages/core/src/Routine.ts | 12 ++++-- packages/core/src/Task.ts | 14 ++++++- packages/core/src/Tool.ts | 8 +++- packages/core/src/constants.ts | 1 - packages/core/src/index.ts | 4 +- packages/emitter/.npmignore | 6 +++ packages/emitter/CHANGELOG.md | 0 packages/emitter/LICENSE | 21 ++++++++++ packages/emitter/README.md | 5 +++ packages/emitter/package.json | 17 ++++++++ packages/{core => emitter}/src/Emitter.ts | 41 +++++++++++-------- packages/emitter/src/constants.ts | 2 + packages/emitter/src/index.ts | 11 +++++ packages/emitter/src/types.ts | 11 +++++ .../{core => emitter}/tests/Emitter.test.ts | 0 16 files changed, 148 insertions(+), 28 deletions(-) create mode 100644 packages/emitter/.npmignore create mode 100644 packages/emitter/CHANGELOG.md create mode 100644 packages/emitter/LICENSE create mode 100644 packages/emitter/README.md create mode 100644 packages/emitter/package.json rename packages/{core => emitter}/src/Emitter.ts (53%) create mode 100644 packages/emitter/src/constants.ts create mode 100644 packages/emitter/src/index.ts create mode 100644 packages/emitter/src/types.ts rename packages/{core => emitter}/tests/Emitter.test.ts (100%) diff --git a/packages/core/src/Console.ts b/packages/core/src/Console.ts index aedca089e..cb4358538 100644 --- a/packages/core/src/Console.ts +++ b/packages/core/src/Console.ts @@ -3,11 +3,12 @@ import exit from 'exit'; import cliSize from 'term-size'; import ansiEscapes from 'ansi-escapes'; -import Emitter from './Emitter'; +import Emitter from '@boost/emitter'; import Tool from './Tool'; import Output from './Output'; import SignalError from './SignalError'; import { Debugger } from './types'; +import { Routine, Task } from '.'; // 8 FPS (60 FPS is actually too fast as it tears) export const FPS_RATE = 125; @@ -25,6 +26,24 @@ export const WRAPPED_STREAMS = { export type StreamType = 'stderr' | 'stdout'; +export interface ConsoleEvents { + error: (error: Error) => void; + routine: (routine: Routine, value: any, parallel: boolean) => void; + 'routine.fail': (routine: Routine, error: Error, parallel: boolean) => void; + 'routine.pass': (routine: Routine, value: any, parallel: boolean) => void; + 'routine.skip': (routine: Routine, value: any, parallel: boolean) => void; + routines: (routines: Routine[], value: any) => void; + 'routines.parallel': (routines: Routine[], value: any) => void; + start: (...args: any[]) => void; + stop: (error: Error) => void; + task: (task: Task, value: any, parallel: boolean) => void; + 'task.fail': (task: Task, error: Error, parallel: boolean) => void; + 'task.pass': (task: Task, value: any, parallel: boolean) => void; + 'task.skip': (task: Task, value: any, parallel: boolean) => void; + tasks: (tasks: Task[], value: any) => void; + 'tasks.parallel': (tasks: Task[], value: any) => void; +} + export interface ConsoleState { disabled: boolean; final: boolean; @@ -32,7 +51,7 @@ export interface ConsoleState { stopped: boolean; } -export default class Console extends Emitter { +export default class Console extends Emitter { bufferedStreams: (() => void)[] = []; debug: Debugger; diff --git a/packages/core/src/Routine.ts b/packages/core/src/Routine.ts index 478595eee..6f934c786 100644 --- a/packages/core/src/Routine.ts +++ b/packages/core/src/Routine.ts @@ -3,7 +3,7 @@ import split from 'split'; import { Readable } from 'stream'; import optimal, { predicates, Blueprint, Predicates } from 'optimal'; import Context from './Context'; -import Task, { TaskAction } from './Task'; +import Task, { TaskAction, TaskEvents } from './Task'; import CoreTool from './Tool'; import { AggregatedResponse } from './Executor'; import ParallelExecutor from './executors/Parallel'; @@ -21,11 +21,17 @@ export interface CommandOptions { wrap?: (process: ExecaChildProcess) => void; } +export interface RoutineEvents extends TaskEvents { + command: (name: string) => void; + 'command.data': (name: string, line: string) => void; +} + export default abstract class Routine< Ctx extends Context, Tool extends CoreTool, - Options extends object = {} -> extends Task { + Options extends object = {}, + Events extends RoutineEvents = RoutineEvents +> extends Task { // @ts-ignore Set after instantiation debug: Debugger; diff --git a/packages/core/src/Task.ts b/packages/core/src/Task.ts index 4be8fc825..5817c8da7 100644 --- a/packages/core/src/Task.ts +++ b/packages/core/src/Task.ts @@ -1,5 +1,5 @@ +import Emitter from '@boost/emitter'; import Context from './Context'; -import Emitter from './Emitter'; import { STATUS_PENDING, STATUS_RUNNING, @@ -15,6 +15,13 @@ export type TaskAction = ( task: Task, ) => any | Promise; +export interface TaskEvents { + fail: (error: Error) => void; + pass: (value: any) => void; + run: (value: any) => void; + skip: (value: any) => void; +} + export interface TaskMetadata { depth: number; index: number; @@ -22,7 +29,10 @@ export interface TaskMetadata { stopTime: number; } -export default class Task extends Emitter { +export default class Task< + Ctx extends Context, + Events extends TaskEvents = TaskEvents +> extends Emitter { action: TaskAction; // @ts-ignore Set after instantiation diff --git a/packages/core/src/Tool.ts b/packages/core/src/Tool.ts index 3d584e3e1..071bbb35e 100644 --- a/packages/core/src/Tool.ts +++ b/packages/core/src/Tool.ts @@ -12,9 +12,9 @@ import i18next from 'i18next'; import mergeWith from 'lodash/mergeWith'; import optimal, { bool, object, string, Blueprint } from 'optimal'; import parseArgs, { Arguments, Options as ArgOptions } from 'yargs-parser'; +import Emitter from '@boost/emitter'; import ConfigLoader from './ConfigLoader'; import Console from './Console'; -import Emitter from './Emitter'; import ExitError from './ExitError'; import ModuleLoader from './ModuleLoader'; import Plugin from './Plugin'; @@ -66,6 +66,10 @@ export interface ToolConfig { theme: string; } +export interface ToolEvents { + exit: (code: number) => void; +} + export interface ToolPluginRegistry { reporter: Reporter; } @@ -73,7 +77,7 @@ export interface ToolPluginRegistry { export default class Tool< PluginRegistry extends ToolPluginRegistry, Config extends ToolConfig = ToolConfig -> extends Emitter { +> extends Emitter { args?: Arguments; argv: string[] = []; diff --git a/packages/core/src/constants.ts b/packages/core/src/constants.ts index 04647cf71..808cf1ce4 100644 --- a/packages/core/src/constants.ts +++ b/packages/core/src/constants.ts @@ -2,7 +2,6 @@ import { Status } from './types'; export const APP_NAME_PATTERN: RegExp = /^[a-z]{1}[-a-z0-9]+[a-z]{1}$/u; export const CONFIG_NAME_PATTERN: RegExp = /^[a-z]{1}[a-zA-Z0-9]+$/u; -export const EVENT_NAME_PATTERN: RegExp = /^[-a-z.]+$/u; export const MODULE_NAME_PATTERN: RegExp = /^(@[-a-z]+\/)?[-a-z]+$/u; export const PLUGIN_NAME_PATTERN: RegExp = /^([a-z]+):[-a-z]+$/u; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 032d6f913..a65211871 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -3,11 +3,11 @@ * @license https://opensource.org/licenses/MIT */ +import Emitter, { EventListener, EventRegistry } from '@boost/emitter'; import CLI from './CLI'; import ConfigLoader from './ConfigLoader'; import Console from './Console'; import Context from './Context'; -import Emitter, { EventArguments, EventListener } from './Emitter'; import ExitError from './ExitError'; import SignalError from './SignalError'; import { AggregatedResponse } from './Executor'; @@ -28,8 +28,8 @@ export { Console, Context, Emitter, - EventArguments, EventListener, + EventRegistry, ExitError, ModuleLoader, Output, diff --git a/packages/emitter/.npmignore b/packages/emitter/.npmignore new file mode 100644 index 000000000..db4e13379 --- /dev/null +++ b/packages/emitter/.npmignore @@ -0,0 +1,6 @@ +src/ +tests/ +types/ +*.lock +*.log +tsconfig.json diff --git a/packages/emitter/CHANGELOG.md b/packages/emitter/CHANGELOG.md new file mode 100644 index 000000000..e69de29bb diff --git a/packages/emitter/LICENSE b/packages/emitter/LICENSE new file mode 100644 index 000000000..6e07fcd5e --- /dev/null +++ b/packages/emitter/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Miles Johnson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/emitter/README.md b/packages/emitter/README.md new file mode 100644 index 000000000..f3ba71329 --- /dev/null +++ b/packages/emitter/README.md @@ -0,0 +1,5 @@ +# Emitter + +[![Build Status](https://travis-ci.org/milesj/boost.svg?branch=master)](https://travis-ci.org/milesj/boost) +[![npm version](https://badge.fury.io/js/%40boost%2Femitter.svg)](https://www.npmjs.com/package/@boost/emitter) +[![npm deps](https://david-dm.org/milesj/boost.svg?path=packages/emitter)](https://www.npmjs.com/package/@boost/emitter) diff --git a/packages/emitter/package.json b/packages/emitter/package.json new file mode 100644 index 000000000..21711d2ab --- /dev/null +++ b/packages/emitter/package.json @@ -0,0 +1,17 @@ +{ + "name": "@boost/emitter", + "version": "0.0.0", + "description": "", + "keywords": [], + "main": "./lib/index.js", + "types": "./lib/index.d.ts", + "engines": { + "node": ">=8.9.0" + }, + "repository": "https://github.com/milesj/boost/tree/master/packages/emitter", + "license": "MIT", + "publishConfig": { + "access": "public" + }, + "priority": 100 +} diff --git a/packages/core/src/Emitter.ts b/packages/emitter/src/Emitter.ts similarity index 53% rename from packages/core/src/Emitter.ts rename to packages/emitter/src/Emitter.ts index bbca97770..9627b4bd8 100644 --- a/packages/core/src/Emitter.ts +++ b/packages/emitter/src/Emitter.ts @@ -1,32 +1,30 @@ import { EVENT_NAME_PATTERN } from './constants'; +import { ListenerOf, ArgumentsOf } from './types'; -export type EventArguments = any[]; - -export type EventListener = (...args: EventArguments) => false | void; - -export default class Emitter { - listeners: Map> = new Map(); +export default class Emitter { + listeners: { [K in keyof T]?: Set> } = {}; /** * Remove all listeners for the defined event name. */ - clearListeners(eventName: string): this { + clearListeners(eventName: K): this { this.getListeners(eventName).clear(); return this; } /** - * Syncronously execute listeners for the defined event and arguments. + * Synchronously execute listeners for the defined event and arguments. + * If a listener returns `false`, the loop with be aborted early. */ - emit(eventName: string, args: EventArguments = []): this { + emit(eventName: K, args: ArgumentsOf): this { Array.from(this.getListeners(eventName)).some(listener => listener(...args) === false); return this; } /** - * Syncronously execute listeners for the defined event and arguments, + * Synchronously execute listeners for the defined event and arguments, * with the ability to intercept and abort early with a value. */ // emitCascade(name: string, args: EventArguments = []): T | void { @@ -44,15 +42,22 @@ export default class Emitter { /** * Return all event names with registered listeners. */ +<<<<<<< HEAD:packages/core/src/Emitter.ts getEventNames(): string[] { return Array.from(this.listeners.keys()); +======= + getEventNames(): (keyof T)[] { + return Object.keys(this.listeners) as (keyof T)[]; +>>>>>>> a8bbc3b... Start moving emitter to own package.:packages/emitter/src/Emitter.ts } /** * Return a set of listeners for a specific event name. */ - getListeners(eventName: string): Set { - if (!eventName.match(EVENT_NAME_PATTERN)) { + getListeners(eventName: K): Set> { + const key = String(eventName); + + if (!key.match(EVENT_NAME_PATTERN)) { throw new Error( `Invalid event name "${eventName}". ` + 'May only contain dashes, periods, and lowercase characters.', @@ -63,22 +68,26 @@ export default class Emitter { this.listeners.set(eventName, new Set()); } +<<<<<<< HEAD:packages/core/src/Emitter.ts return this.listeners.get(eventName)!; +======= + return this.listeners[eventName]!; +>>>>>>> a8bbc3b... Start moving emitter to own package.:packages/emitter/src/Emitter.ts } /** - * Remove a listener function from a specific event name. + * Remove a listener from a specific event name. */ - off(eventName: string, listener: EventListener): this { + off(eventName: K, listener: ListenerOf): this { this.getListeners(eventName).delete(listener); return this; } /** - * Register a listener function to a specific event name. + * Register a listener to a specific event name. */ - on(eventName: string, listener: EventListener): this { + on(eventName: K, listener: ListenerOf): this { if (typeof listener !== 'function') { throw new TypeError(`Invalid event listener for "${eventName}", must be a function.`); } diff --git a/packages/emitter/src/constants.ts b/packages/emitter/src/constants.ts new file mode 100644 index 000000000..544e00409 --- /dev/null +++ b/packages/emitter/src/constants.ts @@ -0,0 +1,2 @@ +// eslint-disable-next-line import/prefer-default-export +export const EVENT_NAME_PATTERN: RegExp = /^[-a-z.]+$/u; diff --git a/packages/emitter/src/index.ts b/packages/emitter/src/index.ts new file mode 100644 index 000000000..f8ea032b1 --- /dev/null +++ b/packages/emitter/src/index.ts @@ -0,0 +1,11 @@ +/** + * @copyright 2017-2019, Miles Johnson + * @license https://opensource.org/licenses/MIT + */ + +import Emitter from './Emitter'; + +export * from './constants'; +export * from './types'; + +export default Emitter; diff --git a/packages/emitter/src/types.ts b/packages/emitter/src/types.ts new file mode 100644 index 000000000..6ac6b17a2 --- /dev/null +++ b/packages/emitter/src/types.ts @@ -0,0 +1,11 @@ +// prettier-ignore +export type ArgumentsOf = + T extends (...args: infer A) => any ? A : + T extends any[] ? T : + never; + +// prettier-ignore +export type ListenerOf = + T extends (...args: any[]) => any ? T : + T extends any[] ? (...args: T) => false | void : + never; diff --git a/packages/core/tests/Emitter.test.ts b/packages/emitter/tests/Emitter.test.ts similarity index 100% rename from packages/core/tests/Emitter.test.ts rename to packages/emitter/tests/Emitter.test.ts From 083701d7f0a0bb660d4a71983446fd71513e0b29 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Sun, 17 Mar 2019 20:10:05 -0700 Subject: [PATCH 02/23] More improvements. Added once. --- packages/emitter/CHANGELOG.md | 14 +++ packages/emitter/package.json | 2 +- packages/emitter/src/Emitter.ts | 17 +++ packages/emitter/src/constants.ts | 2 +- packages/emitter/src/types.ts | 4 +- packages/emitter/tests/Emitter.test.ts | 105 +++++++++++++----- .../tests/__snapshots__/Emitter.test.ts.snap | 7 ++ packages/emitter/tests/typings.ts | 49 ++++++++ 8 files changed, 169 insertions(+), 31 deletions(-) create mode 100644 packages/emitter/tests/__snapshots__/Emitter.test.ts.snap create mode 100644 packages/emitter/tests/typings.ts diff --git a/packages/emitter/CHANGELOG.md b/packages/emitter/CHANGELOG.md index e69de29bb..43865645d 100644 --- a/packages/emitter/CHANGELOG.md +++ b/packages/emitter/CHANGELOG.md @@ -0,0 +1,14 @@ +# 1.0.0 + +#### 🎉 Release + +- Initial release! + +#### 🚀 Updates + +- Added `Emitter#once`, which only fires the listener one time. + +#### 🛠 Internals + +- **[ts]** Refactored the type system to strictly and explicitly type all possible events, + listeners, and their arguments. diff --git a/packages/emitter/package.json b/packages/emitter/package.json index 21711d2ab..4caf78872 100644 --- a/packages/emitter/package.json +++ b/packages/emitter/package.json @@ -1,7 +1,7 @@ { "name": "@boost/emitter", "version": "0.0.0", - "description": "", + "description": "A type-safe event emitter. Designed for Boost applications.", "keywords": [], "main": "./lib/index.js", "types": "./lib/index.d.ts", diff --git a/packages/emitter/src/Emitter.ts b/packages/emitter/src/Emitter.ts index 9627b4bd8..65523a0bd 100644 --- a/packages/emitter/src/Emitter.ts +++ b/packages/emitter/src/Emitter.ts @@ -96,4 +96,21 @@ export default class Emitter { return this; } + + /** + * Register a listener to a specific event name that only triggers once. + */ + once(eventName: K, listener: ListenerOf): this { + if (typeof listener !== 'function') { + throw new TypeError(`Invalid event listener for "${eventName}", must be a function.`); + } + + const handler: any = (...args: ArgumentsOf) => { + this.off(eventName, handler); + + return listener(...args); + }; + + return this.on(eventName, handler); + } } diff --git a/packages/emitter/src/constants.ts b/packages/emitter/src/constants.ts index 544e00409..93e8914f6 100644 --- a/packages/emitter/src/constants.ts +++ b/packages/emitter/src/constants.ts @@ -1,2 +1,2 @@ // eslint-disable-next-line import/prefer-default-export -export const EVENT_NAME_PATTERN: RegExp = /^[-a-z.]+$/u; +export const EVENT_NAME_PATTERN: RegExp = /^[a-z]{1}[-.a-z0-9]*[a-z]{1}$/u; diff --git a/packages/emitter/src/types.ts b/packages/emitter/src/types.ts index 6ac6b17a2..0169eaf23 100644 --- a/packages/emitter/src/types.ts +++ b/packages/emitter/src/types.ts @@ -6,6 +6,6 @@ export type ArgumentsOf = // prettier-ignore export type ListenerOf = - T extends (...args: any[]) => any ? T : - T extends any[] ? (...args: T) => false | void : + T extends (...args: any[]) => boolean | any ? T : + T extends any[] ? (...args: T) => boolean | void : never; diff --git a/packages/emitter/tests/Emitter.test.ts b/packages/emitter/tests/Emitter.test.ts index f88d2f2dc..1a462d1e9 100644 --- a/packages/emitter/tests/Emitter.test.ts +++ b/packages/emitter/tests/Emitter.test.ts @@ -1,7 +1,14 @@ import Emitter from '../src/Emitter'; describe('Emitter', () => { - let emitter: Emitter; + let emitter: Emitter<{ + foo: [number, number, number?]; + bar: [string, string, string]; + baz: []; + qux: () => number; + 'ns.one': [boolean]; + 'ns.two': []; + }>; beforeEach(() => { emitter = new Emitter(); @@ -33,7 +40,7 @@ describe('Emitter', () => { emitter.on('foo', () => { output += 'C'; }); - emitter.emit('foo'); + emitter.emit('foo', [0, 0]); expect(output).toBe('ABC'); }); @@ -58,20 +65,20 @@ describe('Emitter', () => { it('executes listeners syncronously while passing values to each', () => { let value = 'foo'; - emitter.on('foo', () => { + emitter.on('baz', () => { value = value.toUpperCase(); }); - emitter.on('foo', () => { + emitter.on('baz', () => { value = value .split('') .reverse() .join(''); }); - emitter.on('foo', () => { + emitter.on('baz', () => { value = `${value}-${value}`; }); - emitter.emit('foo'); + emitter.emit('baz', []); expect(value).toBe('OOF-OOF'); }); @@ -79,17 +86,17 @@ describe('Emitter', () => { it('executes listeners syncronously with arguments while passing values to each', () => { const value: string[] = []; - emitter.on('foo', a => { + emitter.on('bar', a => { value.push(a.repeat(3)); }); - emitter.on('foo', (a, b) => { + emitter.on('bar', (a, b) => { value.push(b.repeat(2)); }); - emitter.on('foo', (a, b, c) => { + emitter.on('bar', (a, b, c) => { value.push(c.repeat(1)); }); - emitter.emit('foo', ['foo', 'bar', 'baz']); + emitter.emit('bar', ['foo', 'bar', 'baz']); expect(value).toEqual(['foofoofoo', 'barbar', 'baz']); }); @@ -97,20 +104,20 @@ describe('Emitter', () => { it('execution can be stopped', () => { let count = 0; - emitter.on('foo', () => { + emitter.on('baz', () => { count += 1; }); - emitter.on('foo', () => false); - emitter.on('foo', () => { + emitter.on('baz', () => false); + emitter.on('baz', () => { count += 1; }); - emitter.emit('foo'); + emitter.emit('baz', []); expect(count).toBe(1); }); it('passes arguments to listeners', () => { - const baseArgs = [1, 2, 3]; + const baseArgs: [number, number, number] = [1, 2, 3]; let args; emitter.on('foo', (...eventArgs) => { @@ -124,17 +131,17 @@ describe('Emitter', () => { it('passes value by modifying event object', () => { let value = 0; - emitter.on('foo', () => { + emitter.on('baz', () => { value += 1; }); - emitter.on('foo', () => { + emitter.on('baz', () => { value += 1; }); - emitter.on('foo', () => { + emitter.on('baz', () => { value += 1; }); - emitter.emit('foo'); + emitter.emit('baz', []); expect(value).toBe(3); }); @@ -143,16 +150,16 @@ describe('Emitter', () => { it('executes listeners in order', () => { let output = ''; - emitter.on('ns.foo', () => { + emitter.on('ns.one', () => { output += 'A'; }); - emitter.on('ns.foo', () => { + emitter.on('ns.one', () => { output += 'B'; }); - emitter.on('ns.foo', () => { + emitter.on('ns.one', () => { output += 'C'; }); - emitter.emit('ns.foo'); + emitter.emit('ns.one', [true]); expect(output).toBe('ABC'); }); @@ -164,15 +171,18 @@ describe('Emitter', () => { emitter.getListeners('foo'); emitter.getListeners('bar'); emitter.getListeners('baz'); - emitter.getListeners('ns.qux'); + emitter.getListeners('ns.two'); - expect(emitter.getEventNames()).toEqual(['foo', 'bar', 'baz', 'ns.qux']); + expect(emitter.getEventNames()).toEqual(['foo', 'bar', 'baz', 'ns.two']); }); }); describe('getListeners()', () => { it('errors if name contains invalid characters', () => { - expect(() => emitter.getListeners('foo+bar')).toThrowErrorMatchingSnapshot(); + expect(() => { + // @ts-ignore Allow invalid name + emitter.getListeners('foo+bar'); + }).toThrowErrorMatchingSnapshot(); }); it('creates the listeners set if it does not exist', () => { @@ -202,7 +212,7 @@ describe('Emitter', () => { describe('on()', () => { it('errors if listener is not a function', () => { expect(() => { - // @ts-ignore + // @ts-ignore Allow invalid type emitter.on('foo', 123); }).toThrowErrorMatchingSnapshot(); }); @@ -217,4 +227,45 @@ describe('Emitter', () => { expect(emitter.getListeners('foo').has(listener)).toBe(true); }); }); + + describe('once()', () => { + it('errors if listener is not a function', () => { + expect(() => { + // @ts-ignore Allow invalid type + emitter.once('foo', 123); + }).toThrowErrorMatchingSnapshot(); + }); + + it('adds a listener to the set', () => { + const listener = () => {}; + + expect(emitter.getListeners('foo').has(listener)).toBe(false); + expect(emitter.getListeners('foo').size).toBe(0); + + emitter.once('foo', listener); + + // Gets wrapped + expect(emitter.getListeners('foo').has(listener)).toBe(false); + expect(emitter.getListeners('foo').size).toBe(1); + }); + + it('only executes once and removes the listener', () => { + let count = 0; + + const listener = () => { + count += 1; + }; + + emitter.once('baz', listener); + + expect(emitter.getListeners('baz').size).toBe(1); + + emitter.emit('baz', []); + emitter.emit('baz', []); + emitter.emit('baz', []); + + expect(count).toBe(1); + expect(emitter.getListeners('baz').size).toBe(0); + }); + }); }); diff --git a/packages/emitter/tests/__snapshots__/Emitter.test.ts.snap b/packages/emitter/tests/__snapshots__/Emitter.test.ts.snap new file mode 100644 index 000000000..b1a13c889 --- /dev/null +++ b/packages/emitter/tests/__snapshots__/Emitter.test.ts.snap @@ -0,0 +1,7 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Emitter getListeners() errors if name contains invalid characters 1`] = `"Invalid event name \\"foo+bar\\". May only contain dashes, periods, and lowercase characters."`; + +exports[`Emitter on() errors if listener is not a function 1`] = `"Invalid event listener for \\"foo\\", must be a function."`; + +exports[`Emitter once() errors if listener is not a function 1`] = `"Invalid event listener for \\"foo\\", must be a function."`; diff --git a/packages/emitter/tests/typings.ts b/packages/emitter/tests/typings.ts new file mode 100644 index 000000000..bcda03936 --- /dev/null +++ b/packages/emitter/tests/typings.ts @@ -0,0 +1,49 @@ +import Emitter from '../src/Emitter'; + +const emitter = new Emitter<{ + args: [number, boolean, string?]; + 'args.func': (num: number, bool: boolean, str?: string) => void; + 'no.args': []; + 'no.args.func': () => void; + 'custom.return': (num: number) => number; +}>(); + +// VALID + +emitter.on('args', () => {}); +emitter.on('args', (num, bool, str) => {}); +emitter.on('args.func', (num, bool, str) => true); +emitter.on('no.args', () => false); +emitter.on('no.args.func', () => {}); +emitter.once('custom.return', num => num); + +emitter.emit('args', [123, true]); +emitter.emit('args.func', [123, true, 'abc']); +emitter.emit('no.args', []); +emitter.emit('no.args.func', []); +emitter.emit('custom.return', [0]); + +// INVALID + +// Unknown event name +emitter.on('unknown.name', () => {}); + +// Extra arg +emitter.on('args', (num, bool, str, other) => {}); + +// Return not boolean or void +emitter.on('no.args', () => { + return {}; +}); + +// Return not number +emitter.on('custom.return', () => {}); + +// Missing args +emitter.emit('args', [123]); + +// Invalid arg type +emitter.emit('args.func', [123, {}, 'abc']); + +// Extra arg +emitter.emit('no.args', ['abc']); From 6cea4d73347cc33e86b0607394bdbe4e9d126b7f Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Sun, 17 Mar 2019 22:13:03 -0700 Subject: [PATCH 03/23] Add more methods. --- packages/emitter/CHANGELOG.md | 3 + packages/emitter/src/Emitter.ts | 66 ++++--- packages/emitter/src/types.ts | 2 + packages/emitter/tests/Emitter.test.ts | 229 +++++++++++++++++++++---- packages/emitter/tests/typings.ts | 14 +- 5 files changed, 253 insertions(+), 61 deletions(-) diff --git a/packages/emitter/CHANGELOG.md b/packages/emitter/CHANGELOG.md index 43865645d..7218ae03e 100644 --- a/packages/emitter/CHANGELOG.md +++ b/packages/emitter/CHANGELOG.md @@ -7,6 +7,9 @@ #### 🚀 Updates - Added `Emitter#once`, which only fires the listener one time. +- Added `Emitter#emitBail`, which will bail the loop if a listener returns `false`. +- Added `Emitter#emitParallel`, which will asynchronously fire listeners and return a promise. +- Added `Emitter#emitWaterfall`, which passes the return value to each listener. #### 🛠 Internals diff --git a/packages/emitter/src/Emitter.ts b/packages/emitter/src/Emitter.ts index 65523a0bd..27675d1d5 100644 --- a/packages/emitter/src/Emitter.ts +++ b/packages/emitter/src/Emitter.ts @@ -1,5 +1,5 @@ import { EVENT_NAME_PATTERN } from './constants'; -import { ListenerOf, ArgumentsOf } from './types'; +import { ListenerOf, ArgumentsOf, WaterfallArgumentOf } from './types'; export default class Emitter { listeners: { [K in keyof T]?: Set> } = {}; @@ -15,29 +15,48 @@ export default class Emitter { /** * Synchronously execute listeners for the defined event and arguments. - * If a listener returns `false`, the loop with be aborted early. */ emit(eventName: K, args: ArgumentsOf): this { - Array.from(this.getListeners(eventName)).some(listener => listener(...args) === false); + Array.from(this.getListeners(eventName)).forEach(listener => { + listener(...args); + }); return this; } /** - * Synchronously execute listeners for the defined event and arguments, - * with the ability to intercept and abort early with a value. + * Synchronously execute listeners for the defined event and arguments. + * If a listener returns `false`, the loop with be aborted early. */ - // emitCascade(name: string, args: EventArguments = []): T | void { - // let value; + emitBail(eventName: K, args: ArgumentsOf): this { + Array.from(this.getListeners(eventName)).some(listener => listener(...args) === false); - // Array.from(this.getListeners(this.createEventName(name))).some(listener => { - // value = listener(...args); + return this; + } - // return typeof value !== 'undefined'; - // }); + /** + * Asynchronously execute listeners for the defined event and arguments. + * Will return a promise with an array of each listener result. + */ + emitParallel(eventName: K, args: ArgumentsOf): Promise { + return Promise.all( + Array.from(this.getListeners(eventName)).map(listener => Promise.resolve(listener(...args))), + ); + } - // return value; - // } + /** + * Synchronously execute listeners for the defined event and value. + * The return value of each listener will be passed as an argument to the next listener. + */ + emitWaterfall( + eventName: K, + value: WaterfallArgumentOf, + ): WaterfallArgumentOf { + return Array.from(this.getListeners(eventName)).reduce( + (nextValue, listener) => listener(nextValue), + value, + ); + } /** * Return all event names with registered listeners. @@ -88,11 +107,7 @@ export default class Emitter { * Register a listener to a specific event name. */ on(eventName: K, listener: ListenerOf): this { - if (typeof listener !== 'function') { - throw new TypeError(`Invalid event listener for "${eventName}", must be a function.`); - } - - this.getListeners(eventName).add(listener); + this.getListeners(eventName).add(this.validateListener(eventName, listener)); return this; } @@ -101,16 +116,21 @@ export default class Emitter { * Register a listener to a specific event name that only triggers once. */ once(eventName: K, listener: ListenerOf): this { - if (typeof listener !== 'function') { - throw new TypeError(`Invalid event listener for "${eventName}", must be a function.`); - } - + const func = this.validateListener(eventName, listener); const handler: any = (...args: ArgumentsOf) => { this.off(eventName, handler); - return listener(...args); + return func(...args); }; return this.on(eventName, handler); } + + protected validateListener(eventName: K, listener: L): L { + if (typeof listener !== 'function') { + throw new TypeError(`Invalid event listener for "${eventName}", must be a function.`); + } + + return listener; + } } diff --git a/packages/emitter/src/types.ts b/packages/emitter/src/types.ts index 0169eaf23..b5659088a 100644 --- a/packages/emitter/src/types.ts +++ b/packages/emitter/src/types.ts @@ -4,6 +4,8 @@ export type ArgumentsOf = T extends any[] ? T : never; +export type WaterfallArgumentOf = T extends (value: infer A) => any ? A : T; + // prettier-ignore export type ListenerOf = T extends (...args: any[]) => boolean | any ? T : diff --git a/packages/emitter/tests/Emitter.test.ts b/packages/emitter/tests/Emitter.test.ts index 1a462d1e9..81357f1c2 100644 --- a/packages/emitter/tests/Emitter.test.ts +++ b/packages/emitter/tests/Emitter.test.ts @@ -8,12 +8,29 @@ describe('Emitter', () => { qux: () => number; 'ns.one': [boolean]; 'ns.two': []; + parallel: (value: number) => Promise; + waterfall: (value: string) => string; + 'waterfall.array': (value: string[]) => string[]; + 'waterfall.object': (value: { [key: string]: string }) => { [key: string]: string }; }>; beforeEach(() => { emitter = new Emitter(); }); + it('passes arguments to listeners', () => { + const baseArgs: [number, number, number] = [1, 2, 3]; + let args; + + emitter.on('foo', (...eventArgs) => { + args = eventArgs; + }); + + emitter.emit('foo', baseArgs); + + expect(args).toEqual(baseArgs); + }); + describe('clearListeners()', () => { it('deletes all listeners', () => { emitter.on('foo', () => {}); @@ -45,7 +62,7 @@ describe('Emitter', () => { expect(output).toBe('ABC'); }); - it('executes listeners syncronously with arguments', () => { + it('executes listeners synchronously with arguments', () => { const output: number[] = []; emitter.on('foo', (a, b) => { @@ -62,7 +79,7 @@ describe('Emitter', () => { expect(output).toEqual([2, 6, 6, 12, 10, 18]); }); - it('executes listeners syncronously while passing values to each', () => { + it('executes listeners synchronously while passing values to each', () => { let value = 'foo'; emitter.on('baz', () => { @@ -83,7 +100,7 @@ describe('Emitter', () => { expect(value).toBe('OOF-OOF'); }); - it('executes listeners syncronously with arguments while passing values to each', () => { + it('executes listeners synchronously with arguments while passing values to each', () => { const value: string[] = []; emitter.on('bar', a => { @@ -101,7 +118,7 @@ describe('Emitter', () => { expect(value).toEqual(['foofoofoo', 'barbar', 'baz']); }); - it('execution can be stopped', () => { + it('execution can be not be stopped (bailed)', () => { let count = 0; emitter.on('baz', () => { @@ -111,57 +128,199 @@ describe('Emitter', () => { emitter.on('baz', () => { count += 1; }); + emitter.on('baz', () => { + count += 1; + }); + emitter.emit('baz', []); - expect(count).toBe(1); + expect(count).toBe(3); }); + }); - it('passes arguments to listeners', () => { - const baseArgs: [number, number, number] = [1, 2, 3]; - let args; + describe('emitBail()', () => { + it('executes listeners in order', () => { + let output = ''; - emitter.on('foo', (...eventArgs) => { - args = eventArgs; + emitter.on('foo', () => { + output += 'A'; }); - emitter.emit('foo', baseArgs); + emitter.on('foo', () => { + output += 'B'; + }); + emitter.on('foo', () => { + output += 'C'; + }); + + emitter.emitBail('foo', [0, 0]); - expect(args).toEqual(baseArgs); + expect(output).toBe('ABC'); }); - it('passes value by modifying event object', () => { - let value = 0; + it('executes listeners synchronously with arguments', () => { + const output: number[] = []; + + emitter.on('foo', (a, b) => { + output.push(a, b * 2); + }); + emitter.on('foo', (a, b) => { + output.push(a * 3, b * 4); + }); + emitter.on('foo', (a, b) => { + output.push(a * 5, b * 6); + }); + + emitter.emitBail('foo', [2, 3]); + + expect(output).toEqual([2, 6, 6, 12, 10, 18]); + }); + + it('executes listeners synchronously while passing values to each', () => { + let value = 'foo'; emitter.on('baz', () => { - value += 1; + value = value.toUpperCase(); }); emitter.on('baz', () => { - value += 1; + value = value + .split('') + .reverse() + .join(''); }); emitter.on('baz', () => { - value += 1; + value = `${value}-${value}`; }); - emitter.emit('baz', []); + emitter.emitBail('baz', []); + + expect(value).toBe('OOF-OOF'); + }); + + it('executes listeners synchronously with arguments while passing values to each', () => { + const value: string[] = []; + + emitter.on('bar', a => { + value.push(a.repeat(3)); + }); + emitter.on('bar', (a, b) => { + value.push(b.repeat(2)); + }); + emitter.on('bar', (a, b, c) => { + value.push(c.repeat(1)); + }); - expect(value).toBe(3); + emitter.emitBail('bar', ['foo', 'bar', 'baz']); + + expect(value).toEqual(['foofoofoo', 'barbar', 'baz']); }); - describe('with namespace', () => { - it('executes listeners in order', () => { - let output = ''; - - emitter.on('ns.one', () => { - output += 'A'; - }); - emitter.on('ns.one', () => { - output += 'B'; - }); - emitter.on('ns.one', () => { - output += 'C'; - }); - emitter.emit('ns.one', [true]); - - expect(output).toBe('ABC'); + it('execution can be stopped', () => { + let count = 0; + + emitter.on('baz', () => { + count += 1; + }); + emitter.on('baz', () => false); + emitter.on('baz', () => { + count += 1; + }); + emitter.on('baz', () => { + count += 1; + }); + + emitter.emitBail('baz', []); + + expect(count).toBe(1); + }); + }); + + describe('emitParallel()', () => { + beforeEach(() => { + jest.useRealTimers(); + }); + + afterEach(() => { + jest.useFakeTimers(); + }); + + it('returns a promise', () => { + expect(emitter.emitParallel('parallel', [0])).toBeInstanceOf(Promise); + }); + + it('executes listeners asynchronously with arguments', async () => { + const output: number[] = []; + + function getRandom() { + return Math.round(Math.random() * (500 - 0) + 0); + } + + emitter.on( + 'parallel', + value => + new Promise(resolve => { + setTimeout(() => { + resolve(value * 2); + }, getRandom()); + }), + ); + emitter.on( + 'parallel', + value => + new Promise(resolve => { + setTimeout(() => { + resolve(value * 3); + }, getRandom()); + }), + ); + emitter.on( + 'parallel', + value => + new Promise(resolve => { + setTimeout(() => { + resolve(value * 4); + }, getRandom()); + }), + ); + + await emitter.emitParallel('parallel', [1]); + + expect(output).not.toEqual([2, 3, 4]); + }); + }); + + describe('emitWaterfall()', () => { + it('executes listeners in order with the value being passed to each function', () => { + emitter.on('waterfall', value => `${value}B`); + emitter.on('waterfall', value => `${value}C`); + emitter.on('waterfall', value => `${value}D`); + + const output = emitter.emitWaterfall('waterfall', 'A'); + + expect(output).toBe('ABCD'); + }); + + it('supports arrays', () => { + emitter.on('waterfall.array', value => [...value, 'B']); + emitter.on('waterfall.array', value => [...value, 'C']); + emitter.on('waterfall.array', value => [...value, 'D']); + + const output = emitter.emitWaterfall('waterfall.array', ['A']); + + expect(output).toEqual(['A', 'B', 'C', 'D']); + }); + + it('supports objects', () => { + emitter.on('waterfall.object', value => ({ ...value, B: 'B' })); + emitter.on('waterfall.object', value => ({ ...value, C: 'C' })); + emitter.on('waterfall.object', value => ({ ...value, D: 'D' })); + + const output = emitter.emitWaterfall('waterfall.object', { A: 'A' }); + + expect(output).toEqual({ + A: 'A', + B: 'B', + C: 'C', + D: 'D', }); }); }); diff --git a/packages/emitter/tests/typings.ts b/packages/emitter/tests/typings.ts index bcda03936..5a0cab51f 100644 --- a/packages/emitter/tests/typings.ts +++ b/packages/emitter/tests/typings.ts @@ -6,6 +6,10 @@ const emitter = new Emitter<{ 'no.args': []; 'no.args.func': () => void; 'custom.return': (num: number) => number; + waterfall: (value: string) => string; + + // INVALID + 'must.be.array': number; }>(); // VALID @@ -32,9 +36,7 @@ emitter.on('unknown.name', () => {}); emitter.on('args', (num, bool, str, other) => {}); // Return not boolean or void -emitter.on('no.args', () => { - return {}; -}); +emitter.on('no.args', () => ({})); // Return not number emitter.on('custom.return', () => {}); @@ -47,3 +49,9 @@ emitter.emit('args.func', [123, {}, 'abc']); // Extra arg emitter.emit('no.args', ['abc']); + +// Args must be an array +emitter.once('must.be.array', 123); + +// Args must be a string +emitter.once('waterfall', (value: number) => value); From 2a17293f2ed114f35e037f70ec450a1d8c035fa8 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Sun, 17 Mar 2019 22:16:22 -0700 Subject: [PATCH 04/23] Rename types. --- packages/core/src/index.ts | 4 +--- packages/emitter/src/Emitter.ts | 24 ++++++++++++------------ packages/emitter/src/types.ts | 6 +++--- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index a65211871..4e8e20e78 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -3,7 +3,7 @@ * @license https://opensource.org/licenses/MIT */ -import Emitter, { EventListener, EventRegistry } from '@boost/emitter'; +import Emitter from '@boost/emitter'; import CLI from './CLI'; import ConfigLoader from './ConfigLoader'; import Console from './Console'; @@ -28,8 +28,6 @@ export { Console, Context, Emitter, - EventListener, - EventRegistry, ExitError, ModuleLoader, Output, diff --git a/packages/emitter/src/Emitter.ts b/packages/emitter/src/Emitter.ts index 27675d1d5..afe4aeaf8 100644 --- a/packages/emitter/src/Emitter.ts +++ b/packages/emitter/src/Emitter.ts @@ -1,8 +1,8 @@ import { EVENT_NAME_PATTERN } from './constants'; -import { ListenerOf, ArgumentsOf, WaterfallArgumentOf } from './types'; +import { ListenerType, Arguments, WaterfallArgument } from './types'; export default class Emitter { - listeners: { [K in keyof T]?: Set> } = {}; + listeners: { [K in keyof T]?: Set> } = {}; /** * Remove all listeners for the defined event name. @@ -16,7 +16,7 @@ export default class Emitter { /** * Synchronously execute listeners for the defined event and arguments. */ - emit(eventName: K, args: ArgumentsOf): this { + emit(eventName: K, args: Arguments): this { Array.from(this.getListeners(eventName)).forEach(listener => { listener(...args); }); @@ -28,7 +28,7 @@ export default class Emitter { * Synchronously execute listeners for the defined event and arguments. * If a listener returns `false`, the loop with be aborted early. */ - emitBail(eventName: K, args: ArgumentsOf): this { + emitBail(eventName: K, args: Arguments): this { Array.from(this.getListeners(eventName)).some(listener => listener(...args) === false); return this; @@ -38,7 +38,7 @@ export default class Emitter { * Asynchronously execute listeners for the defined event and arguments. * Will return a promise with an array of each listener result. */ - emitParallel(eventName: K, args: ArgumentsOf): Promise { + emitParallel(eventName: K, args: Arguments): Promise { return Promise.all( Array.from(this.getListeners(eventName)).map(listener => Promise.resolve(listener(...args))), ); @@ -50,8 +50,8 @@ export default class Emitter { */ emitWaterfall( eventName: K, - value: WaterfallArgumentOf, - ): WaterfallArgumentOf { + value: WaterfallArgument, + ): WaterfallArgument { return Array.from(this.getListeners(eventName)).reduce( (nextValue, listener) => listener(nextValue), value, @@ -73,7 +73,7 @@ export default class Emitter { /** * Return a set of listeners for a specific event name. */ - getListeners(eventName: K): Set> { + getListeners(eventName: K): Set> { const key = String(eventName); if (!key.match(EVENT_NAME_PATTERN)) { @@ -97,7 +97,7 @@ export default class Emitter { /** * Remove a listener from a specific event name. */ - off(eventName: K, listener: ListenerOf): this { + off(eventName: K, listener: ListenerType): this { this.getListeners(eventName).delete(listener); return this; @@ -106,7 +106,7 @@ export default class Emitter { /** * Register a listener to a specific event name. */ - on(eventName: K, listener: ListenerOf): this { + on(eventName: K, listener: ListenerType): this { this.getListeners(eventName).add(this.validateListener(eventName, listener)); return this; @@ -115,9 +115,9 @@ export default class Emitter { /** * Register a listener to a specific event name that only triggers once. */ - once(eventName: K, listener: ListenerOf): this { + once(eventName: K, listener: ListenerType): this { const func = this.validateListener(eventName, listener); - const handler: any = (...args: ArgumentsOf) => { + const handler: any = (...args: Arguments) => { this.off(eventName, handler); return func(...args); diff --git a/packages/emitter/src/types.ts b/packages/emitter/src/types.ts index b5659088a..5063ea2a1 100644 --- a/packages/emitter/src/types.ts +++ b/packages/emitter/src/types.ts @@ -1,13 +1,13 @@ // prettier-ignore -export type ArgumentsOf = +export type Arguments = T extends (...args: infer A) => any ? A : T extends any[] ? T : never; -export type WaterfallArgumentOf = T extends (value: infer A) => any ? A : T; +export type WaterfallArgument = T extends (value: infer A) => any ? A : T; // prettier-ignore -export type ListenerOf = +export type ListenerType = T extends (...args: any[]) => boolean | any ? T : T extends any[] ? (...args: T) => boolean | void : never; From 21864919dc22261564680f12f495b9aac31b3e71 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 18 Mar 2019 19:21:05 -0700 Subject: [PATCH 05/23] More testing. --- packages/core/src/Console.ts | 32 +++++++++-------- packages/core/src/Routine.ts | 4 +-- packages/core/src/Task.ts | 15 ++++---- packages/core/src/Tool.ts | 2 +- packages/emitter/src/types.ts | 16 +++++++++ packages/emitter/tests/Emitter.test.ts | 50 +++++++++++++------------- packages/emitter/tests/typings.ts | 2 +- 7 files changed, 68 insertions(+), 53 deletions(-) diff --git a/packages/core/src/Console.ts b/packages/core/src/Console.ts index cb4358538..681a88e13 100644 --- a/packages/core/src/Console.ts +++ b/packages/core/src/Console.ts @@ -27,21 +27,23 @@ export const WRAPPED_STREAMS = { export type StreamType = 'stderr' | 'stdout'; export interface ConsoleEvents { - error: (error: Error) => void; - routine: (routine: Routine, value: any, parallel: boolean) => void; - 'routine.fail': (routine: Routine, error: Error, parallel: boolean) => void; - 'routine.pass': (routine: Routine, value: any, parallel: boolean) => void; - 'routine.skip': (routine: Routine, value: any, parallel: boolean) => void; - routines: (routines: Routine[], value: any) => void; - 'routines.parallel': (routines: Routine[], value: any) => void; - start: (...args: any[]) => void; - stop: (error: Error) => void; - task: (task: Task, value: any, parallel: boolean) => void; - 'task.fail': (task: Task, error: Error, parallel: boolean) => void; - 'task.pass': (task: Task, value: any, parallel: boolean) => void; - 'task.skip': (task: Task, value: any, parallel: boolean) => void; - tasks: (tasks: Task[], value: any) => void; - 'tasks.parallel': (tasks: Task[], value: any) => void; + command: [string, Routine]; + 'command.data': [string, string, Routine]; + error: [Error]; + routine: [Routine, any, boolean]; + 'routine.fail': [Routine, Error, boolean]; + 'routine.pass': [Routine, any, boolean]; + 'routine.skip': [Routine, any, boolean]; + routines: [Routine[], any]; + 'routines.parallel': [Routine[], any]; + start: any[]; + stop: [Error | null]; + task: [Task, any, boolean]; + 'task.fail': [Task, Error, boolean]; + 'task.pass': [Task, any, boolean]; + 'task.skip': [Task, any, boolean]; + tasks: [Task[], any]; + 'tasks.parallel': [Task[], any]; } export interface ConsoleState { diff --git a/packages/core/src/Routine.ts b/packages/core/src/Routine.ts index 6f934c786..10d54e457 100644 --- a/packages/core/src/Routine.ts +++ b/packages/core/src/Routine.ts @@ -22,8 +22,8 @@ export interface CommandOptions { } export interface RoutineEvents extends TaskEvents { - command: (name: string) => void; - 'command.data': (name: string, line: string) => void; + command: [string]; + 'command.data': [string, string]; } export default abstract class Routine< diff --git a/packages/core/src/Task.ts b/packages/core/src/Task.ts index 5817c8da7..9c67ec826 100644 --- a/packages/core/src/Task.ts +++ b/packages/core/src/Task.ts @@ -16,10 +16,10 @@ export type TaskAction = ( ) => any | Promise; export interface TaskEvents { - fail: (error: Error) => void; - pass: (value: any) => void; - run: (value: any) => void; - skip: (value: any) => void; + fail: [Error]; + pass: [any]; + run: [any]; + skip: [any]; } export interface TaskMetadata { @@ -29,10 +29,7 @@ export interface TaskMetadata { stopTime: number; } -export default class Task< - Ctx extends Context, - Events extends TaskEvents = TaskEvents -> extends Emitter { +export default class Task extends Emitter { action: TaskAction; // @ts-ignore Set after instantiation @@ -49,7 +46,7 @@ export default class Task< output: string = ''; - parent: Task | null = null; + parent: Task | null = null; status: Status = STATUS_PENDING; diff --git a/packages/core/src/Tool.ts b/packages/core/src/Tool.ts index 071bbb35e..dbb906394 100644 --- a/packages/core/src/Tool.ts +++ b/packages/core/src/Tool.ts @@ -67,7 +67,7 @@ export interface ToolConfig { } export interface ToolEvents { - exit: (code: number) => void; + exit: [number]; } export interface ToolPluginRegistry { diff --git a/packages/emitter/src/types.ts b/packages/emitter/src/types.ts index 5063ea2a1..5058ade2a 100644 --- a/packages/emitter/src/types.ts +++ b/packages/emitter/src/types.ts @@ -4,6 +4,14 @@ export type Arguments = T extends any[] ? T : never; +// type A = Arguments<[number]>; +// type B = Arguments<[number, string]>; +// type C = Arguments<[number, boolean, string?]>; +// type D = Arguments<() => void>; +// type E = Arguments<(a: string) => boolean | void>; +// type F = Arguments<(a: string, b: number) => boolean | void>; +// type G = Arguments<(a: string, b: number, c?: object) => string>; + export type WaterfallArgument = T extends (value: infer A) => any ? A : T; // prettier-ignore @@ -11,3 +19,11 @@ export type ListenerType = T extends (...args: any[]) => boolean | any ? T : T extends any[] ? (...args: T) => boolean | void : never; + +// type H = ListenerType<(a: string, b: number, c?: object) => string>; +// type I = ListenerType<(a: string) => Promise>; +// type J = ListenerType<(a: string, b: number) => boolean | void>; +// type K = ListenerType<[number, boolean, string?]>; +// type L = ListenerType<[number]>; +// type M = ListenerType<[]>; +// type N = ListenerType<{}>; diff --git a/packages/emitter/tests/Emitter.test.ts b/packages/emitter/tests/Emitter.test.ts index 81357f1c2..ae2d0102a 100644 --- a/packages/emitter/tests/Emitter.test.ts +++ b/packages/emitter/tests/Emitter.test.ts @@ -22,7 +22,7 @@ describe('Emitter', () => { const baseArgs: [number, number, number] = [1, 2, 3]; let args; - emitter.on('foo', (...eventArgs) => { + emitter.on('foo', (...eventArgs: any[]) => { args = eventArgs; }); @@ -65,13 +65,13 @@ describe('Emitter', () => { it('executes listeners synchronously with arguments', () => { const output: number[] = []; - emitter.on('foo', (a, b) => { + emitter.on('foo', (a: number, b: number) => { output.push(a, b * 2); }); - emitter.on('foo', (a, b) => { + emitter.on('foo', (a: number, b: number) => { output.push(a * 3, b * 4); }); - emitter.on('foo', (a, b) => { + emitter.on('foo', (a: number, b: number) => { output.push(a * 5, b * 6); }); emitter.emit('foo', [2, 3]); @@ -103,13 +103,13 @@ describe('Emitter', () => { it('executes listeners synchronously with arguments while passing values to each', () => { const value: string[] = []; - emitter.on('bar', a => { + emitter.on('bar', (a: string) => { value.push(a.repeat(3)); }); - emitter.on('bar', (a, b) => { + emitter.on('bar', (a: string, b: string) => { value.push(b.repeat(2)); }); - emitter.on('bar', (a, b, c) => { + emitter.on('bar', (a: string, b: string, c: string) => { value.push(c.repeat(1)); }); @@ -160,13 +160,13 @@ describe('Emitter', () => { it('executes listeners synchronously with arguments', () => { const output: number[] = []; - emitter.on('foo', (a, b) => { + emitter.on('foo', (a: number, b: number) => { output.push(a, b * 2); }); - emitter.on('foo', (a, b) => { + emitter.on('foo', (a: number, b: number) => { output.push(a * 3, b * 4); }); - emitter.on('foo', (a, b) => { + emitter.on('foo', (a: number, b: number) => { output.push(a * 5, b * 6); }); @@ -199,13 +199,13 @@ describe('Emitter', () => { it('executes listeners synchronously with arguments while passing values to each', () => { const value: string[] = []; - emitter.on('bar', a => { + emitter.on('bar', (a: string) => { value.push(a.repeat(3)); }); - emitter.on('bar', (a, b) => { + emitter.on('bar', (a: string, b: string) => { value.push(b.repeat(2)); }); - emitter.on('bar', (a, b, c) => { + emitter.on('bar', (a: string, b: string, c: string) => { value.push(c.repeat(1)); }); @@ -256,7 +256,7 @@ describe('Emitter', () => { emitter.on( 'parallel', - value => + (value: number) => new Promise(resolve => { setTimeout(() => { resolve(value * 2); @@ -265,7 +265,7 @@ describe('Emitter', () => { ); emitter.on( 'parallel', - value => + (value: number) => new Promise(resolve => { setTimeout(() => { resolve(value * 3); @@ -274,7 +274,7 @@ describe('Emitter', () => { ); emitter.on( 'parallel', - value => + (value: number) => new Promise(resolve => { setTimeout(() => { resolve(value * 4); @@ -290,9 +290,9 @@ describe('Emitter', () => { describe('emitWaterfall()', () => { it('executes listeners in order with the value being passed to each function', () => { - emitter.on('waterfall', value => `${value}B`); - emitter.on('waterfall', value => `${value}C`); - emitter.on('waterfall', value => `${value}D`); + emitter.on('waterfall', (value: string) => `${value}B`); + emitter.on('waterfall', (value: string) => `${value}C`); + emitter.on('waterfall', (value: string) => `${value}D`); const output = emitter.emitWaterfall('waterfall', 'A'); @@ -300,9 +300,9 @@ describe('Emitter', () => { }); it('supports arrays', () => { - emitter.on('waterfall.array', value => [...value, 'B']); - emitter.on('waterfall.array', value => [...value, 'C']); - emitter.on('waterfall.array', value => [...value, 'D']); + emitter.on('waterfall.array', (value: string[]) => [...value, 'B']); + emitter.on('waterfall.array', (value: string[]) => [...value, 'C']); + emitter.on('waterfall.array', (value: string[]) => [...value, 'D']); const output = emitter.emitWaterfall('waterfall.array', ['A']); @@ -310,9 +310,9 @@ describe('Emitter', () => { }); it('supports objects', () => { - emitter.on('waterfall.object', value => ({ ...value, B: 'B' })); - emitter.on('waterfall.object', value => ({ ...value, C: 'C' })); - emitter.on('waterfall.object', value => ({ ...value, D: 'D' })); + emitter.on('waterfall.object', (value: { [key: string]: string }) => ({ ...value, B: 'B' })); + emitter.on('waterfall.object', (value: { [key: string]: string }) => ({ ...value, C: 'C' })); + emitter.on('waterfall.object', (value: { [key: string]: string }) => ({ ...value, D: 'D' })); const output = emitter.emitWaterfall('waterfall.object', { A: 'A' }); diff --git a/packages/emitter/tests/typings.ts b/packages/emitter/tests/typings.ts index 5a0cab51f..ad140d0a6 100644 --- a/packages/emitter/tests/typings.ts +++ b/packages/emitter/tests/typings.ts @@ -19,7 +19,7 @@ emitter.on('args', (num, bool, str) => {}); emitter.on('args.func', (num, bool, str) => true); emitter.on('no.args', () => false); emitter.on('no.args.func', () => {}); -emitter.once('custom.return', num => num); +emitter.once('custom.return', (num: number) => num); emitter.emit('args', [123, true]); emitter.emit('args.func', [123, true, 'abc']); From 3d709ca17c150fb225686e40e3f7d25230f80ed9 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 18 Mar 2019 19:52:14 -0700 Subject: [PATCH 06/23] Use unknown for inferrence. --- packages/core/src/Task.ts | 5 ++++- packages/emitter/src/types.ts | 12 +++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/core/src/Task.ts b/packages/core/src/Task.ts index 9c67ec826..0b3f4af92 100644 --- a/packages/core/src/Task.ts +++ b/packages/core/src/Task.ts @@ -29,7 +29,10 @@ export interface TaskMetadata { stopTime: number; } -export default class Task extends Emitter { +export default class Task< + Ctx extends Context, + Events extends TaskEvents = TaskEvents +> extends Emitter { action: TaskAction; // @ts-ignore Set after instantiation diff --git a/packages/emitter/src/types.ts b/packages/emitter/src/types.ts index 5058ade2a..e0e99e135 100644 --- a/packages/emitter/src/types.ts +++ b/packages/emitter/src/types.ts @@ -1,9 +1,10 @@ // prettier-ignore export type Arguments = - T extends (...args: infer A) => any ? A : - T extends any[] ? T : + T extends (...args: infer A) => unknown ? A : + T extends unknown[] ? T : never; +// VALID // type A = Arguments<[number]>; // type B = Arguments<[number, string]>; // type C = Arguments<[number, boolean, string?]>; @@ -12,14 +13,15 @@ export type Arguments = // type F = Arguments<(a: string, b: number) => boolean | void>; // type G = Arguments<(a: string, b: number, c?: object) => string>; -export type WaterfallArgument = T extends (value: infer A) => any ? A : T; +export type WaterfallArgument = T extends (value: infer A) => unknown ? A : T; // prettier-ignore export type ListenerType = - T extends (...args: any[]) => boolean | any ? T : - T extends any[] ? (...args: T) => boolean | void : + T extends (...args: any[]) => any ? T : + T extends unknown[] ? (...args: T) => boolean | void : never; +// VALID // type H = ListenerType<(a: string, b: number, c?: object) => string>; // type I = ListenerType<(a: string) => Promise>; // type J = ListenerType<(a: string, b: number) => boolean | void>; From 371f863d44ae570de69fd50b39b8186a9d0d14b3 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Sat, 23 Mar 2019 14:53:22 -0700 Subject: [PATCH 07/23] More stuff. --- packages/core/package.json | 1 + packages/core/src/Task.ts | 10 +++++----- packages/emitter/src/Emitter.ts | 15 +++------------ packages/emitter/src/types.ts | 23 +++++++++++++++++++++-- packages/emitter/tests/Emitter.test.ts | 25 +++++++++++++------------ packages/emitter/tests/typings.ts | 17 +++++++++-------- 6 files changed, 52 insertions(+), 39 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index 87938faa5..fa2e2650e 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -22,6 +22,7 @@ "access": "public" }, "dependencies": { + "@boost/emitter": "^0.0.0", "@types/cli-truncate": "^1.1.0", "@types/debug": "^4.1.2", "@types/execa": "^0.9.0", diff --git a/packages/core/src/Task.ts b/packages/core/src/Task.ts index 0b3f4af92..926afe25d 100644 --- a/packages/core/src/Task.ts +++ b/packages/core/src/Task.ts @@ -1,4 +1,4 @@ -import Emitter from '@boost/emitter'; +import Emitter, { Listener } from '@boost/emitter'; import Context from './Context'; import { STATUS_PENDING, @@ -16,10 +16,10 @@ export type TaskAction = ( ) => any | Promise; export interface TaskEvents { - fail: [Error]; - pass: [any]; - run: [any]; - skip: [any]; + fail: Listener<[Error]>; + pass: Listener<[any]>; + run: Listener<[any]>; + skip: Listener<[any]>; } export interface TaskMetadata { diff --git a/packages/emitter/src/Emitter.ts b/packages/emitter/src/Emitter.ts index afe4aeaf8..1ea4069ba 100644 --- a/packages/emitter/src/Emitter.ts +++ b/packages/emitter/src/Emitter.ts @@ -1,5 +1,5 @@ import { EVENT_NAME_PATTERN } from './constants'; -import { ListenerType, Arguments, WaterfallArgument } from './types'; +import { Arguments, WaterfallArgument, ListenerType } from './types'; export default class Emitter { listeners: { [K in keyof T]?: Set> } = {}; @@ -61,13 +61,8 @@ export default class Emitter { /** * Return all event names with registered listeners. */ -<<<<<<< HEAD:packages/core/src/Emitter.ts - getEventNames(): string[] { - return Array.from(this.listeners.keys()); -======= getEventNames(): (keyof T)[] { return Object.keys(this.listeners) as (keyof T)[]; ->>>>>>> a8bbc3b... Start moving emitter to own package.:packages/emitter/src/Emitter.ts } /** @@ -83,15 +78,11 @@ export default class Emitter { ); } - if (!this.listeners.has(eventName)) { - this.listeners.set(eventName, new Set()); + if (!this.listeners[eventName]) { + this.listeners[eventName] = new Set(); } -<<<<<<< HEAD:packages/core/src/Emitter.ts - return this.listeners.get(eventName)!; -======= return this.listeners[eventName]!; ->>>>>>> a8bbc3b... Start moving emitter to own package.:packages/emitter/src/Emitter.ts } /** diff --git a/packages/emitter/src/types.ts b/packages/emitter/src/types.ts index e0e99e135..7fc760283 100644 --- a/packages/emitter/src/types.ts +++ b/packages/emitter/src/types.ts @@ -1,10 +1,29 @@ +export type Listener = (...args: T) => R; + +export type ParallelListener = Listener>; + +export type WaterfallListener = Listener<[T], T>; + +// VALID +// type A = Listener<[string]>; +// type B = Listener<[string, number], object>; +// type C = ParallelListener<[string, number]>; +// type F = WaterfallListener; + // prettier-ignore export type Arguments = T extends (...args: infer A) => unknown ? A : - T extends unknown[] ? T : + // T extends unknown[] ? T : never; // VALID +type A = Arguments>; +type B = Arguments>; +type C = Arguments>; +type D = Arguments; +type E = Arguments<(a: string) => boolean | void>; +type F = Arguments<(a: string, b: number) => boolean | void>; +type G = Arguments<(a: string, b: number, c?: object) => string>; // type A = Arguments<[number]>; // type B = Arguments<[number, string]>; // type C = Arguments<[number, boolean, string?]>; @@ -18,7 +37,7 @@ export type WaterfallArgument = T extends (value: infer A) => unknown ? A : T // prettier-ignore export type ListenerType = T extends (...args: any[]) => any ? T : - T extends unknown[] ? (...args: T) => boolean | void : + // T extends unknown[] ? (...args: T) => boolean | void : never; // VALID diff --git a/packages/emitter/tests/Emitter.test.ts b/packages/emitter/tests/Emitter.test.ts index ae2d0102a..ee6a5552e 100644 --- a/packages/emitter/tests/Emitter.test.ts +++ b/packages/emitter/tests/Emitter.test.ts @@ -1,17 +1,18 @@ import Emitter from '../src/Emitter'; +import { Listener, ParallelListener, WaterfallListener } from '../src/types'; describe('Emitter', () => { let emitter: Emitter<{ - foo: [number, number, number?]; - bar: [string, string, string]; - baz: []; - qux: () => number; - 'ns.one': [boolean]; - 'ns.two': []; - parallel: (value: number) => Promise; - waterfall: (value: string) => string; - 'waterfall.array': (value: string[]) => string[]; - 'waterfall.object': (value: { [key: string]: string }) => { [key: string]: string }; + foo: Listener<[number, number, number?]>; + bar: Listener<[string, string, string]>; + baz: Listener; + qux: Listener<[], number>; + 'ns.one': Listener<[boolean]>; + 'ns.two': Listener; + parallel: ParallelListener<[number]>; + waterfall: WaterfallListener; + 'waterfall.array': WaterfallListener; + 'waterfall.object': WaterfallListener<{ [key: string]: string }>; }>; beforeEach(() => { @@ -345,12 +346,12 @@ describe('Emitter', () => { }); it('creates the listeners set if it does not exist', () => { - expect(emitter.listeners.has('foo')).toBe(false); + expect(emitter.listeners.foo).toBeUndefined(); const set = emitter.getListeners('foo'); expect(set).toBeInstanceOf(Set); - expect(emitter.listeners.has('foo')).toBe(true); + expect(emitter.listeners.foo).toBeUndefined(); }); }); diff --git a/packages/emitter/tests/typings.ts b/packages/emitter/tests/typings.ts index ad140d0a6..a9bc76f99 100644 --- a/packages/emitter/tests/typings.ts +++ b/packages/emitter/tests/typings.ts @@ -1,12 +1,13 @@ import Emitter from '../src/Emitter'; +import { Listener, WaterfallListener } from '../src/types'; const emitter = new Emitter<{ - args: [number, boolean, string?]; - 'args.func': (num: number, bool: boolean, str?: string) => void; - 'no.args': []; - 'no.args.func': () => void; - 'custom.return': (num: number) => number; - waterfall: (value: string) => string; + args: Listener<[number, boolean, string]>; + 'args.func': Listener<[number, boolean, string?]>; + 'no.args': Listener; + 'no.args.func': Listener<[]>; + 'custom.return': Listener<[number], number>; + waterfall: WaterfallListener; // INVALID 'must.be.array': number; @@ -21,8 +22,8 @@ emitter.on('no.args', () => false); emitter.on('no.args.func', () => {}); emitter.once('custom.return', (num: number) => num); -emitter.emit('args', [123, true]); -emitter.emit('args.func', [123, true, 'abc']); +emitter.emit('args', [123, true, 'abc']); +emitter.emit('args.func', [123, true]); emitter.emit('no.args', []); emitter.emit('no.args.func', []); emitter.emit('custom.return', [0]); From 1f67cf1bb85a4a595bc088ed97de58095db10e90 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 25 Mar 2019 23:33:09 -0700 Subject: [PATCH 08/23] More. --- packages/core/src/Console.ts | 36 +++++----- packages/core/src/Routine.ts | 12 +--- packages/core/src/Task.ts | 22 +++--- packages/core/src/Tool.ts | 4 +- packages/emitter/src/Emitter.ts | 16 ++--- packages/emitter/src/types.ts | 92 ++++++++++++-------------- packages/emitter/tests/Emitter.test.ts | 14 ++-- packages/emitter/tests/typings.ts | 38 +++++------ 8 files changed, 108 insertions(+), 126 deletions(-) diff --git a/packages/core/src/Console.ts b/packages/core/src/Console.ts index 681a88e13..3f4de4d41 100644 --- a/packages/core/src/Console.ts +++ b/packages/core/src/Console.ts @@ -3,7 +3,7 @@ import exit from 'exit'; import cliSize from 'term-size'; import ansiEscapes from 'ansi-escapes'; -import Emitter from '@boost/emitter'; +import Emitter, { Listener } from '@boost/emitter'; import Tool from './Tool'; import Output from './Output'; import SignalError from './SignalError'; @@ -27,23 +27,23 @@ export const WRAPPED_STREAMS = { export type StreamType = 'stderr' | 'stdout'; export interface ConsoleEvents { - command: [string, Routine]; - 'command.data': [string, string, Routine]; - error: [Error]; - routine: [Routine, any, boolean]; - 'routine.fail': [Routine, Error, boolean]; - 'routine.pass': [Routine, any, boolean]; - 'routine.skip': [Routine, any, boolean]; - routines: [Routine[], any]; - 'routines.parallel': [Routine[], any]; - start: any[]; - stop: [Error | null]; - task: [Task, any, boolean]; - 'task.fail': [Task, Error, boolean]; - 'task.pass': [Task, any, boolean]; - 'task.skip': [Task, any, boolean]; - tasks: [Task[], any]; - 'tasks.parallel': [Task[], any]; + command: Listener>; + 'command.data': Listener>; + error: Listener; + routine: Listener, any, boolean>; + 'routine.fail': Listener, Error, boolean>; + 'routine.pass': Listener, any, boolean>; + 'routine.skip': Listener, any, boolean>; + routines: Listener[], any>; + 'routines.parallel': Listener[], any>; + start: Listener; + stop: Listener; + task: Listener, any, boolean>; + 'task.fail': Listener, Error, boolean>; + 'task.pass': Listener, any, boolean>; + 'task.skip': Listener, any, boolean>; + tasks: Listener[], any>; + 'tasks.parallel': Listener[], any>; } export interface ConsoleState { diff --git a/packages/core/src/Routine.ts b/packages/core/src/Routine.ts index 10d54e457..478595eee 100644 --- a/packages/core/src/Routine.ts +++ b/packages/core/src/Routine.ts @@ -3,7 +3,7 @@ import split from 'split'; import { Readable } from 'stream'; import optimal, { predicates, Blueprint, Predicates } from 'optimal'; import Context from './Context'; -import Task, { TaskAction, TaskEvents } from './Task'; +import Task, { TaskAction } from './Task'; import CoreTool from './Tool'; import { AggregatedResponse } from './Executor'; import ParallelExecutor from './executors/Parallel'; @@ -21,17 +21,11 @@ export interface CommandOptions { wrap?: (process: ExecaChildProcess) => void; } -export interface RoutineEvents extends TaskEvents { - command: [string]; - 'command.data': [string, string]; -} - export default abstract class Routine< Ctx extends Context, Tool extends CoreTool, - Options extends object = {}, - Events extends RoutineEvents = RoutineEvents -> extends Task { + Options extends object = {} +> extends Task { // @ts-ignore Set after instantiation debug: Debugger; diff --git a/packages/core/src/Task.ts b/packages/core/src/Task.ts index 926afe25d..678096f17 100644 --- a/packages/core/src/Task.ts +++ b/packages/core/src/Task.ts @@ -13,13 +13,16 @@ export type TaskAction = ( context: Ctx, value: any, task: Task, -) => any | Promise; +) => unknown | Promise; export interface TaskEvents { - fail: Listener<[Error]>; - pass: Listener<[any]>; - run: Listener<[any]>; - skip: Listener<[any]>; + fail: Listener; + pass: Listener; + run: Listener; + skip: Listener; + // Routine + command: Listener; + 'command.data': Listener; } export interface TaskMetadata { @@ -29,10 +32,7 @@ export interface TaskMetadata { stopTime: number; } -export default class Task< - Ctx extends Context, - Events extends TaskEvents = TaskEvents -> extends Emitter { +export default class Task extends Emitter { action: TaskAction; // @ts-ignore Set after instantiation @@ -47,9 +47,9 @@ export default class Task< stopTime: 0, }; - output: string = ''; + output: unknown = ''; - parent: Task | null = null; + parent: Task | null = null; status: Status = STATUS_PENDING; diff --git a/packages/core/src/Tool.ts b/packages/core/src/Tool.ts index dbb906394..451031e75 100644 --- a/packages/core/src/Tool.ts +++ b/packages/core/src/Tool.ts @@ -12,7 +12,7 @@ import i18next from 'i18next'; import mergeWith from 'lodash/mergeWith'; import optimal, { bool, object, string, Blueprint } from 'optimal'; import parseArgs, { Arguments, Options as ArgOptions } from 'yargs-parser'; -import Emitter from '@boost/emitter'; +import Emitter, { Listener } from '@boost/emitter'; import ConfigLoader from './ConfigLoader'; import Console from './Console'; import ExitError from './ExitError'; @@ -67,7 +67,7 @@ export interface ToolConfig { } export interface ToolEvents { - exit: [number]; + exit: Listener; } export interface ToolPluginRegistry { diff --git a/packages/emitter/src/Emitter.ts b/packages/emitter/src/Emitter.ts index 1ea4069ba..d7cdb7211 100644 --- a/packages/emitter/src/Emitter.ts +++ b/packages/emitter/src/Emitter.ts @@ -16,7 +16,7 @@ export default class Emitter { /** * Synchronously execute listeners for the defined event and arguments. */ - emit(eventName: K, args: Arguments): this { + emit>(eventName: K, args: A): this { Array.from(this.getListeners(eventName)).forEach(listener => { listener(...args); }); @@ -28,7 +28,7 @@ export default class Emitter { * Synchronously execute listeners for the defined event and arguments. * If a listener returns `false`, the loop with be aborted early. */ - emitBail(eventName: K, args: Arguments): this { + emitBail>(eventName: K, args: A): this { Array.from(this.getListeners(eventName)).some(listener => listener(...args) === false); return this; @@ -38,7 +38,10 @@ export default class Emitter { * Asynchronously execute listeners for the defined event and arguments. * Will return a promise with an array of each listener result. */ - emitParallel(eventName: K, args: Arguments): Promise { + emitParallel>( + eventName: K, + args: A, + ): Promise { return Promise.all( Array.from(this.getListeners(eventName)).map(listener => Promise.resolve(listener(...args))), ); @@ -48,12 +51,9 @@ export default class Emitter { * Synchronously execute listeners for the defined event and value. * The return value of each listener will be passed as an argument to the next listener. */ - emitWaterfall( - eventName: K, - value: WaterfallArgument, - ): WaterfallArgument { + emitWaterfall>(eventName: K, value: V): V { return Array.from(this.getListeners(eventName)).reduce( - (nextValue, listener) => listener(nextValue), + (nextValue, listener) => listener(nextValue) as V, value, ); } diff --git a/packages/emitter/src/types.ts b/packages/emitter/src/types.ts index 7fc760283..fc5151900 100644 --- a/packages/emitter/src/types.ts +++ b/packages/emitter/src/types.ts @@ -1,50 +1,42 @@ -export type Listener = (...args: T) => R; - -export type ParallelListener = Listener>; - -export type WaterfallListener = Listener<[T], T>; - -// VALID -// type A = Listener<[string]>; -// type B = Listener<[string, number], object>; -// type C = ParallelListener<[string, number]>; -// type F = WaterfallListener; - -// prettier-ignore -export type Arguments = - T extends (...args: infer A) => unknown ? A : - // T extends unknown[] ? T : - never; - -// VALID -type A = Arguments>; -type B = Arguments>; -type C = Arguments>; -type D = Arguments; -type E = Arguments<(a: string) => boolean | void>; -type F = Arguments<(a: string, b: number) => boolean | void>; -type G = Arguments<(a: string, b: number, c?: object) => string>; -// type A = Arguments<[number]>; -// type B = Arguments<[number, string]>; -// type C = Arguments<[number, boolean, string?]>; -// type D = Arguments<() => void>; -// type E = Arguments<(a: string) => boolean | void>; -// type F = Arguments<(a: string, b: number) => boolean | void>; -// type G = Arguments<(a: string, b: number, c?: object) => string>; - -export type WaterfallArgument = T extends (value: infer A) => unknown ? A : T; - -// prettier-ignore -export type ListenerType = - T extends (...args: any[]) => any ? T : - // T extends unknown[] ? (...args: T) => boolean | void : - never; - -// VALID -// type H = ListenerType<(a: string, b: number, c?: object) => string>; -// type I = ListenerType<(a: string) => Promise>; -// type J = ListenerType<(a: string, b: number) => boolean | void>; -// type K = ListenerType<[number, boolean, string?]>; -// type L = ListenerType<[number]>; -// type M = ListenerType<[]>; -// type N = ListenerType<{}>; +export type BaseListener< + R, + T1 = undefined, + T2 = undefined, + T3 = undefined, + T4 = undefined, + T5 = undefined +> = T1 extends undefined + ? () => R + : T2 extends undefined + ? (a1: T1) => R + : T3 extends undefined + ? (a1: T1, a2: T2) => R + : T4 extends undefined + ? (a1: T1, a2: T2, a3: T3) => R + : T5 extends undefined + ? (a1: T1, a2: T2, a3: T3, a4: T4) => R + : (a1: T1, a2: T2, a3: T3, a4: T4, a5: T5) => R; + +export type Listener< + T1 = undefined, + T2 = undefined, + T3 = undefined, + T4 = undefined, + T5 = undefined +> = BaseListener; + +export type ParallelListener< + T1 = undefined, + T2 = undefined, + T3 = undefined, + T4 = undefined, + T5 = undefined +> = BaseListener, T1, T2, T3, T4, T5>; + +export type WaterfallListener = BaseListener; + +export type ListenerType = T extends (...args: unknown[]) => unknown ? T : never; + +export type Arguments = T extends (...args: infer A) => unknown ? A : never; + +export type WaterfallArgument = T extends (arg: infer A) => unknown ? A : never; diff --git a/packages/emitter/tests/Emitter.test.ts b/packages/emitter/tests/Emitter.test.ts index ee6a5552e..bb3632f9d 100644 --- a/packages/emitter/tests/Emitter.test.ts +++ b/packages/emitter/tests/Emitter.test.ts @@ -1,15 +1,15 @@ import Emitter from '../src/Emitter'; -import { Listener, ParallelListener, WaterfallListener } from '../src/types'; +import { BaseListener, Listener, ParallelListener, WaterfallListener } from '../src/types'; describe('Emitter', () => { let emitter: Emitter<{ - foo: Listener<[number, number, number?]>; - bar: Listener<[string, string, string]>; + foo: Listener; + bar: Listener; baz: Listener; - qux: Listener<[], number>; - 'ns.one': Listener<[boolean]>; + qux: BaseListener; + 'ns.one': Listener; 'ns.two': Listener; - parallel: ParallelListener<[number]>; + parallel: ParallelListener; waterfall: WaterfallListener; 'waterfall.array': WaterfallListener; 'waterfall.object': WaterfallListener<{ [key: string]: string }>; @@ -20,7 +20,7 @@ describe('Emitter', () => { }); it('passes arguments to listeners', () => { - const baseArgs: [number, number, number] = [1, 2, 3]; + const baseArgs: [number, number] = [1, 2]; let args; emitter.on('foo', (...eventArgs: any[]) => { diff --git a/packages/emitter/tests/typings.ts b/packages/emitter/tests/typings.ts index a9bc76f99..a86eadd0b 100644 --- a/packages/emitter/tests/typings.ts +++ b/packages/emitter/tests/typings.ts @@ -1,32 +1,25 @@ import Emitter from '../src/Emitter'; -import { Listener, WaterfallListener } from '../src/types'; +import { Listener, WaterfallListener, ParallelListener } from '../src/types'; const emitter = new Emitter<{ - args: Listener<[number, boolean, string]>; - 'args.func': Listener<[number, boolean, string?]>; + args: Listener; 'no.args': Listener; - 'no.args.func': Listener<[]>; - 'custom.return': Listener<[number], number>; - waterfall: WaterfallListener; - - // INVALID - 'must.be.array': number; + parallel: ParallelListener; + waterfall: WaterfallListener; }>(); // VALID emitter.on('args', () => {}); emitter.on('args', (num, bool, str) => {}); -emitter.on('args.func', (num, bool, str) => true); emitter.on('no.args', () => false); -emitter.on('no.args.func', () => {}); -emitter.once('custom.return', (num: number) => num); +emitter.on('parallel', () => Promise.resolve()); +emitter.on('waterfall', str => str); emitter.emit('args', [123, true, 'abc']); -emitter.emit('args.func', [123, true]); -emitter.emit('no.args', []); -emitter.emit('no.args.func', []); -emitter.emit('custom.return', [0]); +emitter.emitBail('no.args', []); +emitter.emitParallel('parallel', [{ test: true }, ['a', 'b', 'c']]); +emitter.emitWaterfall('waterfall', 'abc'); // INVALID @@ -39,20 +32,23 @@ emitter.on('args', (num, bool, str, other) => {}); // Return not boolean or void emitter.on('no.args', () => ({})); -// Return not number -emitter.on('custom.return', () => {}); +// Return not promise +emitter.on('parallel', () => {}); // Missing args emitter.emit('args', [123]); // Invalid arg type -emitter.emit('args.func', [123, {}, 'abc']); +emitter.emit('args', [123, {}, 'abc']); // Extra arg emitter.emit('no.args', ['abc']); -// Args must be an array -emitter.once('must.be.array', 123); +// Empty args +emitter.emitParallel('parallel', []); + +// Waterfall only 1 arg +emitter.emitWaterfall('waterfall', ['foo']); // Args must be a string emitter.once('waterfall', (value: number) => value); From 5c7b53dc863011d06cc132d04d37020d77af9fb7 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Tue, 26 Mar 2019 21:07:44 -0700 Subject: [PATCH 09/23] Try event objects. --- packages/core/src/Task.ts | 20 ++-- packages/emitter/src/BailEvent.ts | 13 +++ packages/emitter/src/BaseEvent.ts | 70 ++++++++++++++ packages/emitter/src/Emitter.ts | 127 ------------------------- packages/emitter/src/Event.ts | 14 +++ packages/emitter/src/ParallelEvent.ts | 16 ++++ packages/emitter/src/WaterfallEvent.ts | 11 +++ packages/emitter/src/index.ts | 8 +- packages/emitter/src/types.ts | 44 +-------- 9 files changed, 138 insertions(+), 185 deletions(-) create mode 100644 packages/emitter/src/BailEvent.ts create mode 100644 packages/emitter/src/BaseEvent.ts delete mode 100644 packages/emitter/src/Emitter.ts create mode 100644 packages/emitter/src/Event.ts create mode 100644 packages/emitter/src/ParallelEvent.ts create mode 100644 packages/emitter/src/WaterfallEvent.ts diff --git a/packages/core/src/Task.ts b/packages/core/src/Task.ts index 678096f17..37f04307b 100644 --- a/packages/core/src/Task.ts +++ b/packages/core/src/Task.ts @@ -1,4 +1,4 @@ -import Emitter, { Listener } from '@boost/emitter'; +import { Event } from '@boost/emitter'; import Context from './Context'; import { STATUS_PENDING, @@ -15,16 +15,6 @@ export type TaskAction = ( task: Task, ) => unknown | Promise; -export interface TaskEvents { - fail: Listener; - pass: Listener; - run: Listener; - skip: Listener; - // Routine - command: Listener; - 'command.data': Listener; -} - export interface TaskMetadata { depth: number; index: number; @@ -32,7 +22,7 @@ export interface TaskMetadata { stopTime: number; } -export default class Task extends Emitter { +export default class Task { action: TaskAction; // @ts-ignore Set after instantiation @@ -47,6 +37,8 @@ export default class Task extends Emitter { stopTime: 0, }; + onFail: Event<[Error | null]>; + output: unknown = ''; parent: Task | null = null; @@ -56,8 +48,6 @@ export default class Task extends Emitter { statusText: string = ''; constructor(title: string, action: TaskAction) { - super(); - if (!title || typeof title !== 'string') { throw new Error('Tasks require a title.'); } @@ -69,6 +59,7 @@ export default class Task extends Emitter { this.action = action; this.status = STATUS_PENDING; this.title = title; + this.onFail = new Event('fail'); } /** @@ -139,6 +130,7 @@ export default class Task extends Emitter { this.status = STATUS_FAILED; this.metadata.stopTime = Date.now(); this.emit('fail', [error]); + this.onFail.listen((a, b, c) => 'foo'); throw error; } diff --git a/packages/emitter/src/BailEvent.ts b/packages/emitter/src/BailEvent.ts new file mode 100644 index 000000000..78ee5c3ed --- /dev/null +++ b/packages/emitter/src/BailEvent.ts @@ -0,0 +1,13 @@ +import BaseEvent from './BaseEvent'; + +export default class BailEvent extends BaseEvent { + /** + * Synchronously execute listeners with the defined arguments. + * If a listener returns `false`, the loop with be aborted early. + */ + emit(args: Args): this { + Array.from(this.listeners).some(listener => listener(...args) === false); + + return this; + } +} diff --git a/packages/emitter/src/BaseEvent.ts b/packages/emitter/src/BaseEvent.ts new file mode 100644 index 000000000..e2239fd81 --- /dev/null +++ b/packages/emitter/src/BaseEvent.ts @@ -0,0 +1,70 @@ +import { EVENT_NAME_PATTERN } from './constants'; +import { Listener } from './types'; + +export default abstract class BaseEvent { + listeners: Set> = new Set(); + + name: string; + + constructor(name: string) { + if (!name.match(EVENT_NAME_PATTERN)) { + throw new Error( + `Invalid event name "${name}". ` + + 'May only contain dashes, periods, and lowercase characters.', + ); + } + + this.name = name; + } + + /** + * Remove all listeners from the event. + */ + clearListeners(): this { + this.listeners.clear(); + + return this; + } + + /** + * Register a listener to the event. + */ + listen(listener: Listener): this { + this.listeners.add(this.validateListener(listener)); + + return this; + } + + /** + * Register a listener to the event that only triggers once. + */ + once(listener: Listener): this { + const func = this.validateListener(listener); + const handler: Listener = (...args: Args) => { + this.unlisten(handler); + + return func(...args); + }; + + return this.listen(handler); + } + + /** + * Remove a listener from the event. + */ + unlisten(listener: Listener): this { + this.listeners.delete(listener); + + return this; + } + + protected validateListener(listener: L): L { + if (typeof listener !== 'function') { + throw new TypeError(`Invalid event listener for "${this.name}", must be a function.`); + } + + return listener; + } + + abstract emit(args: unknown): unknown; +} diff --git a/packages/emitter/src/Emitter.ts b/packages/emitter/src/Emitter.ts deleted file mode 100644 index d7cdb7211..000000000 --- a/packages/emitter/src/Emitter.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { EVENT_NAME_PATTERN } from './constants'; -import { Arguments, WaterfallArgument, ListenerType } from './types'; - -export default class Emitter { - listeners: { [K in keyof T]?: Set> } = {}; - - /** - * Remove all listeners for the defined event name. - */ - clearListeners(eventName: K): this { - this.getListeners(eventName).clear(); - - return this; - } - - /** - * Synchronously execute listeners for the defined event and arguments. - */ - emit>(eventName: K, args: A): this { - Array.from(this.getListeners(eventName)).forEach(listener => { - listener(...args); - }); - - return this; - } - - /** - * Synchronously execute listeners for the defined event and arguments. - * If a listener returns `false`, the loop with be aborted early. - */ - emitBail>(eventName: K, args: A): this { - Array.from(this.getListeners(eventName)).some(listener => listener(...args) === false); - - return this; - } - - /** - * Asynchronously execute listeners for the defined event and arguments. - * Will return a promise with an array of each listener result. - */ - emitParallel>( - eventName: K, - args: A, - ): Promise { - return Promise.all( - Array.from(this.getListeners(eventName)).map(listener => Promise.resolve(listener(...args))), - ); - } - - /** - * Synchronously execute listeners for the defined event and value. - * The return value of each listener will be passed as an argument to the next listener. - */ - emitWaterfall>(eventName: K, value: V): V { - return Array.from(this.getListeners(eventName)).reduce( - (nextValue, listener) => listener(nextValue) as V, - value, - ); - } - - /** - * Return all event names with registered listeners. - */ - getEventNames(): (keyof T)[] { - return Object.keys(this.listeners) as (keyof T)[]; - } - - /** - * Return a set of listeners for a specific event name. - */ - getListeners(eventName: K): Set> { - const key = String(eventName); - - if (!key.match(EVENT_NAME_PATTERN)) { - throw new Error( - `Invalid event name "${eventName}". ` + - 'May only contain dashes, periods, and lowercase characters.', - ); - } - - if (!this.listeners[eventName]) { - this.listeners[eventName] = new Set(); - } - - return this.listeners[eventName]!; - } - - /** - * Remove a listener from a specific event name. - */ - off(eventName: K, listener: ListenerType): this { - this.getListeners(eventName).delete(listener); - - return this; - } - - /** - * Register a listener to a specific event name. - */ - on(eventName: K, listener: ListenerType): this { - this.getListeners(eventName).add(this.validateListener(eventName, listener)); - - return this; - } - - /** - * Register a listener to a specific event name that only triggers once. - */ - once(eventName: K, listener: ListenerType): this { - const func = this.validateListener(eventName, listener); - const handler: any = (...args: Arguments) => { - this.off(eventName, handler); - - return func(...args); - }; - - return this.on(eventName, handler); - } - - protected validateListener(eventName: K, listener: L): L { - if (typeof listener !== 'function') { - throw new TypeError(`Invalid event listener for "${eventName}", must be a function.`); - } - - return listener; - } -} diff --git a/packages/emitter/src/Event.ts b/packages/emitter/src/Event.ts new file mode 100644 index 000000000..46b5db556 --- /dev/null +++ b/packages/emitter/src/Event.ts @@ -0,0 +1,14 @@ +import BaseEvent from './BaseEvent'; + +export default class Event extends BaseEvent { + /** + * Synchronously execute listeners with the defined arguments. + */ + emit(args: Args): this { + Array.from(this.listeners).forEach(listener => { + listener(...args); + }); + + return this; + } +} diff --git a/packages/emitter/src/ParallelEvent.ts b/packages/emitter/src/ParallelEvent.ts new file mode 100644 index 000000000..5a4caeb92 --- /dev/null +++ b/packages/emitter/src/ParallelEvent.ts @@ -0,0 +1,16 @@ +import BaseEvent from './BaseEvent'; + +export default class ParallelEvent extends BaseEvent< + Args, + Promise +> { + /** + * Asynchronously execute listeners for with the defined arguments. + * Will return a promise with an array of each listener result. + */ + emit(args: Args): Promise { + return Promise.all( + Array.from(this.listeners).map(listener => Promise.resolve(listener(...args))), + ); + } +} diff --git a/packages/emitter/src/WaterfallEvent.ts b/packages/emitter/src/WaterfallEvent.ts new file mode 100644 index 000000000..5aacc7c92 --- /dev/null +++ b/packages/emitter/src/WaterfallEvent.ts @@ -0,0 +1,11 @@ +import BaseEvent from './BaseEvent'; + +export default class WaterfallEvent extends BaseEvent<[Arg], Arg> { + /** + * Synchronously execute listeners with the defined argument value. + * The return value of each listener will be passed as an argument to the next listener. + */ + emit(arg: Arg): Arg { + return Array.from(this.listeners).reduce((nextValue, listener) => listener(nextValue), arg); + } +} diff --git a/packages/emitter/src/index.ts b/packages/emitter/src/index.ts index f8ea032b1..22cd8fdff 100644 --- a/packages/emitter/src/index.ts +++ b/packages/emitter/src/index.ts @@ -3,9 +3,13 @@ * @license https://opensource.org/licenses/MIT */ -import Emitter from './Emitter'; +import BailEvent from './BailEvent'; +import BaseEvent from './BaseEvent'; +import Event from './Event'; +import ParallelEvent from './ParallelEvent'; +import WaterfallEvent from './WaterfallEvent'; export * from './constants'; export * from './types'; -export default Emitter; +export { BailEvent, BaseEvent, Event, ParallelEvent, WaterfallEvent }; diff --git a/packages/emitter/src/types.ts b/packages/emitter/src/types.ts index fc5151900..537b6b38e 100644 --- a/packages/emitter/src/types.ts +++ b/packages/emitter/src/types.ts @@ -1,42 +1,2 @@ -export type BaseListener< - R, - T1 = undefined, - T2 = undefined, - T3 = undefined, - T4 = undefined, - T5 = undefined -> = T1 extends undefined - ? () => R - : T2 extends undefined - ? (a1: T1) => R - : T3 extends undefined - ? (a1: T1, a2: T2) => R - : T4 extends undefined - ? (a1: T1, a2: T2, a3: T3) => R - : T5 extends undefined - ? (a1: T1, a2: T2, a3: T3, a4: T4) => R - : (a1: T1, a2: T2, a3: T3, a4: T4, a5: T5) => R; - -export type Listener< - T1 = undefined, - T2 = undefined, - T3 = undefined, - T4 = undefined, - T5 = undefined -> = BaseListener; - -export type ParallelListener< - T1 = undefined, - T2 = undefined, - T3 = undefined, - T4 = undefined, - T5 = undefined -> = BaseListener, T1, T2, T3, T4, T5>; - -export type WaterfallListener = BaseListener; - -export type ListenerType = T extends (...args: unknown[]) => unknown ? T : never; - -export type Arguments = T extends (...args: infer A) => unknown ? A : never; - -export type WaterfallArgument = T extends (arg: infer A) => unknown ? A : never; +// eslint-disable-next-line import/prefer-default-export +export type Listener = (...args: A) => R; From 430c48845a9d16c9de234dbb5cc5ac5af7628eee Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Wed, 27 Mar 2019 20:16:23 -0700 Subject: [PATCH 10/23] Rename package to event. Add scope. --- packages/core/package.json | 2 +- packages/core/src/Console.ts | 2 +- packages/core/src/Task.ts | 2 +- packages/core/src/Tool.ts | 2 +- packages/core/src/index.ts | 2 +- packages/emitter/src/BaseEvent.ts | 70 ------------- packages/{emitter => event}/.npmignore | 0 packages/{emitter => event}/CHANGELOG.md | 0 packages/{emitter => event}/LICENSE | 0 packages/{emitter => event}/README.md | 6 +- packages/{emitter => event}/package.json | 4 +- packages/{emitter => event}/src/BailEvent.ts | 5 +- packages/event/src/BaseEvent.ts | 99 +++++++++++++++++++ packages/{emitter => event}/src/Event.ts | 5 +- .../{emitter => event}/src/ParallelEvent.ts | 7 +- .../{emitter => event}/src/WaterfallEvent.ts | 8 +- packages/{emitter => event}/src/constants.ts | 0 packages/{emitter => event}/src/index.ts | 0 packages/{emitter => event}/src/types.ts | 3 +- .../{emitter => event}/tests/Emitter.test.ts | 0 .../tests/__snapshots__/Emitter.test.ts.snap | 0 packages/{emitter => event}/tests/typings.ts | 0 22 files changed, 126 insertions(+), 91 deletions(-) delete mode 100644 packages/emitter/src/BaseEvent.ts rename packages/{emitter => event}/.npmignore (100%) rename packages/{emitter => event}/CHANGELOG.md (100%) rename packages/{emitter => event}/LICENSE (100%) rename packages/{emitter => event}/README.md (67%) rename packages/{emitter => event}/package.json (89%) rename packages/{emitter => event}/src/BailEvent.ts (65%) create mode 100644 packages/event/src/BaseEvent.ts rename packages/{emitter => event}/src/Event.ts (64%) rename packages/{emitter => event}/src/ParallelEvent.ts (61%) rename packages/{emitter => event}/src/WaterfallEvent.ts (59%) rename packages/{emitter => event}/src/constants.ts (100%) rename packages/{emitter => event}/src/index.ts (100%) rename packages/{emitter => event}/src/types.ts (53%) rename packages/{emitter => event}/tests/Emitter.test.ts (100%) rename packages/{emitter => event}/tests/__snapshots__/Emitter.test.ts.snap (100%) rename packages/{emitter => event}/tests/typings.ts (100%) diff --git a/packages/core/package.json b/packages/core/package.json index fa2e2650e..b12f3fec4 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -22,7 +22,7 @@ "access": "public" }, "dependencies": { - "@boost/emitter": "^0.0.0", + "@boost/event": "^0.0.0", "@types/cli-truncate": "^1.1.0", "@types/debug": "^4.1.2", "@types/execa": "^0.9.0", diff --git a/packages/core/src/Console.ts b/packages/core/src/Console.ts index 3f4de4d41..6c2590c9a 100644 --- a/packages/core/src/Console.ts +++ b/packages/core/src/Console.ts @@ -3,7 +3,7 @@ import exit from 'exit'; import cliSize from 'term-size'; import ansiEscapes from 'ansi-escapes'; -import Emitter, { Listener } from '@boost/emitter'; +import Emitter, { Listener } from '@boost/event'; import Tool from './Tool'; import Output from './Output'; import SignalError from './SignalError'; diff --git a/packages/core/src/Task.ts b/packages/core/src/Task.ts index 37f04307b..ce850eaa5 100644 --- a/packages/core/src/Task.ts +++ b/packages/core/src/Task.ts @@ -1,4 +1,4 @@ -import { Event } from '@boost/emitter'; +import { Event } from '@boost/event'; import Context from './Context'; import { STATUS_PENDING, diff --git a/packages/core/src/Tool.ts b/packages/core/src/Tool.ts index 451031e75..1a5462b6a 100644 --- a/packages/core/src/Tool.ts +++ b/packages/core/src/Tool.ts @@ -12,7 +12,7 @@ import i18next from 'i18next'; import mergeWith from 'lodash/mergeWith'; import optimal, { bool, object, string, Blueprint } from 'optimal'; import parseArgs, { Arguments, Options as ArgOptions } from 'yargs-parser'; -import Emitter, { Listener } from '@boost/emitter'; +import Emitter, { Listener } from '@boost/event'; import ConfigLoader from './ConfigLoader'; import Console from './Console'; import ExitError from './ExitError'; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 4e8e20e78..8a83e6eee 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -3,7 +3,7 @@ * @license https://opensource.org/licenses/MIT */ -import Emitter from '@boost/emitter'; +import Emitter from '@boost/event'; import CLI from './CLI'; import ConfigLoader from './ConfigLoader'; import Console from './Console'; diff --git a/packages/emitter/src/BaseEvent.ts b/packages/emitter/src/BaseEvent.ts deleted file mode 100644 index e2239fd81..000000000 --- a/packages/emitter/src/BaseEvent.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { EVENT_NAME_PATTERN } from './constants'; -import { Listener } from './types'; - -export default abstract class BaseEvent { - listeners: Set> = new Set(); - - name: string; - - constructor(name: string) { - if (!name.match(EVENT_NAME_PATTERN)) { - throw new Error( - `Invalid event name "${name}". ` + - 'May only contain dashes, periods, and lowercase characters.', - ); - } - - this.name = name; - } - - /** - * Remove all listeners from the event. - */ - clearListeners(): this { - this.listeners.clear(); - - return this; - } - - /** - * Register a listener to the event. - */ - listen(listener: Listener): this { - this.listeners.add(this.validateListener(listener)); - - return this; - } - - /** - * Register a listener to the event that only triggers once. - */ - once(listener: Listener): this { - const func = this.validateListener(listener); - const handler: Listener = (...args: Args) => { - this.unlisten(handler); - - return func(...args); - }; - - return this.listen(handler); - } - - /** - * Remove a listener from the event. - */ - unlisten(listener: Listener): this { - this.listeners.delete(listener); - - return this; - } - - protected validateListener(listener: L): L { - if (typeof listener !== 'function') { - throw new TypeError(`Invalid event listener for "${this.name}", must be a function.`); - } - - return listener; - } - - abstract emit(args: unknown): unknown; -} diff --git a/packages/emitter/.npmignore b/packages/event/.npmignore similarity index 100% rename from packages/emitter/.npmignore rename to packages/event/.npmignore diff --git a/packages/emitter/CHANGELOG.md b/packages/event/CHANGELOG.md similarity index 100% rename from packages/emitter/CHANGELOG.md rename to packages/event/CHANGELOG.md diff --git a/packages/emitter/LICENSE b/packages/event/LICENSE similarity index 100% rename from packages/emitter/LICENSE rename to packages/event/LICENSE diff --git a/packages/emitter/README.md b/packages/event/README.md similarity index 67% rename from packages/emitter/README.md rename to packages/event/README.md index f3ba71329..f5989e8a6 100644 --- a/packages/emitter/README.md +++ b/packages/event/README.md @@ -1,5 +1,5 @@ -# Emitter +# Event [![Build Status](https://travis-ci.org/milesj/boost.svg?branch=master)](https://travis-ci.org/milesj/boost) -[![npm version](https://badge.fury.io/js/%40boost%2Femitter.svg)](https://www.npmjs.com/package/@boost/emitter) -[![npm deps](https://david-dm.org/milesj/boost.svg?path=packages/emitter)](https://www.npmjs.com/package/@boost/emitter) +[![npm version](https://badge.fury.io/js/%40boost%2Femitter.svg)](https://www.npmjs.com/package/@boost/event) +[![npm deps](https://david-dm.org/milesj/boost.svg?path=packages/event)](https://www.npmjs.com/package/@boost/event) diff --git a/packages/emitter/package.json b/packages/event/package.json similarity index 89% rename from packages/emitter/package.json rename to packages/event/package.json index 4caf78872..9fe25eb84 100644 --- a/packages/emitter/package.json +++ b/packages/event/package.json @@ -1,5 +1,5 @@ { - "name": "@boost/emitter", + "name": "@boost/event", "version": "0.0.0", "description": "A type-safe event emitter. Designed for Boost applications.", "keywords": [], @@ -8,7 +8,7 @@ "engines": { "node": ">=8.9.0" }, - "repository": "https://github.com/milesj/boost/tree/master/packages/emitter", + "repository": "https://github.com/milesj/boost/tree/master/packages/event", "license": "MIT", "publishConfig": { "access": "public" diff --git a/packages/emitter/src/BailEvent.ts b/packages/event/src/BailEvent.ts similarity index 65% rename from packages/emitter/src/BailEvent.ts rename to packages/event/src/BailEvent.ts index 78ee5c3ed..6b7d59472 100644 --- a/packages/emitter/src/BailEvent.ts +++ b/packages/event/src/BailEvent.ts @@ -1,12 +1,13 @@ import BaseEvent from './BaseEvent'; +import { Scope } from './types'; export default class BailEvent extends BaseEvent { /** * Synchronously execute listeners with the defined arguments. * If a listener returns `false`, the loop with be aborted early. */ - emit(args: Args): this { - Array.from(this.listeners).some(listener => listener(...args) === false); + emit(args: Args, scope?: Scope): this { + Array.from(this.getListeners(scope)).some(listener => listener(...args) === false); return this; } diff --git a/packages/event/src/BaseEvent.ts b/packages/event/src/BaseEvent.ts new file mode 100644 index 000000000..268db4b96 --- /dev/null +++ b/packages/event/src/BaseEvent.ts @@ -0,0 +1,99 @@ +import { EVENT_NAME_PATTERN } from './constants'; +import { Listener, Scope } from './types'; + +export default abstract class BaseEvent { + listeners: Map>> = new Map(); + + name: string; + + constructor(name: string) { + this.name = this.validateName(name, 'name'); + } + + /** + * Remove all listeners from the event. + */ + clearListeners(scope?: Scope): this { + if (scope) { + this.getListeners(scope).clear(); + } else { + this.listeners.clear(); + } + + return this; + } + + /** + * Return a set of listeners for a specific event scope. + */ + getListeners(scope: Scope = '*'): Set> { + const key = this.validateName(scope, 'scope'); + + if (!this.listeners.has(key)) { + this.listeners.set(key, new Set()); + } + + return this.listeners.get(key)!; + } + + /** + * Register a listener to the event. + */ + listen(listener: Listener, scope?: Scope): this { + this.getListeners(scope).add(this.validateListener(listener)); + + return this; + } + + /** + * Register a listener to the event that only triggers once. + */ + once(listener: Listener, scope?: Scope): this { + const func = this.validateListener(listener); + const handler: Listener = (...args: Args) => { + this.unlisten(handler); + + return func(...args); + }; + + return this.listen(handler, scope); + } + + /** + * Remove a listener from the event. + */ + unlisten(listener: Listener, scope?: Scope): this { + this.getListeners(scope).delete(listener); + + return this; + } + + /** + * Validate the listener is a function. + */ + protected validateListener(listener: L): L { + if (typeof listener !== 'function') { + throw new TypeError(`Invalid event listener for "${this.name}", must be a function.`); + } + + return listener; + } + + /** + * Validate the name/scope match a defined pattern. + */ + protected validateName(name: string, type: string): string { + if (!name.match(EVENT_NAME_PATTERN)) { + throw new Error( + `Invalid event ${type} "${name}". May only contain dashes, periods, and lowercase characters.`, + ); + } + + return name; + } + + /** + * Emit the event by executing all scoped listeners with the defined arguments. + */ + abstract emit(args: unknown, scope?: Scope): unknown; +} diff --git a/packages/emitter/src/Event.ts b/packages/event/src/Event.ts similarity index 64% rename from packages/emitter/src/Event.ts rename to packages/event/src/Event.ts index 46b5db556..86b537013 100644 --- a/packages/emitter/src/Event.ts +++ b/packages/event/src/Event.ts @@ -1,11 +1,12 @@ import BaseEvent from './BaseEvent'; +import { Scope } from './types'; export default class Event extends BaseEvent { /** * Synchronously execute listeners with the defined arguments. */ - emit(args: Args): this { - Array.from(this.listeners).forEach(listener => { + emit(args: Args, scope?: Scope): this { + Array.from(this.getListeners(scope)).forEach(listener => { listener(...args); }); diff --git a/packages/emitter/src/ParallelEvent.ts b/packages/event/src/ParallelEvent.ts similarity index 61% rename from packages/emitter/src/ParallelEvent.ts rename to packages/event/src/ParallelEvent.ts index 5a4caeb92..e73e13b7f 100644 --- a/packages/emitter/src/ParallelEvent.ts +++ b/packages/event/src/ParallelEvent.ts @@ -1,4 +1,5 @@ import BaseEvent from './BaseEvent'; +import { Scope } from './types'; export default class ParallelEvent extends BaseEvent< Args, @@ -8,9 +9,7 @@ export default class ParallelEvent extends BaseEvent< * Asynchronously execute listeners for with the defined arguments. * Will return a promise with an array of each listener result. */ - emit(args: Args): Promise { - return Promise.all( - Array.from(this.listeners).map(listener => Promise.resolve(listener(...args))), - ); + emit(args: Args, scope?: Scope): Promise { + return Promise.all(Array.from(this.getListeners(scope)).map(listener => listener(...args))); } } diff --git a/packages/emitter/src/WaterfallEvent.ts b/packages/event/src/WaterfallEvent.ts similarity index 59% rename from packages/emitter/src/WaterfallEvent.ts rename to packages/event/src/WaterfallEvent.ts index 5aacc7c92..25bd9e024 100644 --- a/packages/emitter/src/WaterfallEvent.ts +++ b/packages/event/src/WaterfallEvent.ts @@ -1,11 +1,15 @@ import BaseEvent from './BaseEvent'; +import { Scope } from './types'; export default class WaterfallEvent extends BaseEvent<[Arg], Arg> { /** * Synchronously execute listeners with the defined argument value. * The return value of each listener will be passed as an argument to the next listener. */ - emit(arg: Arg): Arg { - return Array.from(this.listeners).reduce((nextValue, listener) => listener(nextValue), arg); + emit(arg: Arg, scope?: Scope): Arg { + return Array.from(this.getListeners(scope)).reduce( + (nextValue, listener) => listener(nextValue), + arg, + ); } } diff --git a/packages/emitter/src/constants.ts b/packages/event/src/constants.ts similarity index 100% rename from packages/emitter/src/constants.ts rename to packages/event/src/constants.ts diff --git a/packages/emitter/src/index.ts b/packages/event/src/index.ts similarity index 100% rename from packages/emitter/src/index.ts rename to packages/event/src/index.ts diff --git a/packages/emitter/src/types.ts b/packages/event/src/types.ts similarity index 53% rename from packages/emitter/src/types.ts rename to packages/event/src/types.ts index 537b6b38e..682a328d0 100644 --- a/packages/emitter/src/types.ts +++ b/packages/event/src/types.ts @@ -1,2 +1,3 @@ -// eslint-disable-next-line import/prefer-default-export export type Listener = (...args: A) => R; + +export type Scope = string; diff --git a/packages/emitter/tests/Emitter.test.ts b/packages/event/tests/Emitter.test.ts similarity index 100% rename from packages/emitter/tests/Emitter.test.ts rename to packages/event/tests/Emitter.test.ts diff --git a/packages/emitter/tests/__snapshots__/Emitter.test.ts.snap b/packages/event/tests/__snapshots__/Emitter.test.ts.snap similarity index 100% rename from packages/emitter/tests/__snapshots__/Emitter.test.ts.snap rename to packages/event/tests/__snapshots__/Emitter.test.ts.snap diff --git a/packages/emitter/tests/typings.ts b/packages/event/tests/typings.ts similarity index 100% rename from packages/emitter/tests/typings.ts rename to packages/event/tests/typings.ts From 65daf69b484a0003be83f4821983bb0f0ea7d7bc Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Wed, 27 Mar 2019 22:14:16 -0700 Subject: [PATCH 11/23] Final changes. --- packages/core/src/Console.ts | 54 ++++++++++---------- packages/core/src/Executor.ts | 46 +++-------------- packages/core/src/Reporter.ts | 3 +- packages/core/src/Routine.ts | 32 ++++++++++-- packages/core/src/Task.ts | 47 ++++++++++++++--- packages/core/src/Tool.ts | 31 +++++++---- packages/core/src/reporters/BoostReporter.ts | 8 +-- packages/core/src/reporters/CIReporter.ts | 19 +++---- packages/core/src/reporters/ErrorReporter.ts | 2 +- packages/event/CHANGELOG.md | 8 +-- packages/event/src/BailEvent.ts | 9 ++-- packages/event/src/BaseEvent.ts | 9 +++- packages/event/src/types.ts | 10 +++- packages/reporter-nyan/src/NyanReporter.ts | 19 ++++--- 14 files changed, 176 insertions(+), 121 deletions(-) diff --git a/packages/core/src/Console.ts b/packages/core/src/Console.ts index 6c2590c9a..124e6ea99 100644 --- a/packages/core/src/Console.ts +++ b/packages/core/src/Console.ts @@ -3,7 +3,7 @@ import exit from 'exit'; import cliSize from 'term-size'; import ansiEscapes from 'ansi-escapes'; -import Emitter, { Listener } from '@boost/event'; +import { Event } from '@boost/event'; import Tool from './Tool'; import Output from './Output'; import SignalError from './SignalError'; @@ -26,26 +26,6 @@ export const WRAPPED_STREAMS = { export type StreamType = 'stderr' | 'stdout'; -export interface ConsoleEvents { - command: Listener>; - 'command.data': Listener>; - error: Listener; - routine: Listener, any, boolean>; - 'routine.fail': Listener, Error, boolean>; - 'routine.pass': Listener, any, boolean>; - 'routine.skip': Listener, any, boolean>; - routines: Listener[], any>; - 'routines.parallel': Listener[], any>; - start: Listener; - stop: Listener; - task: Listener, any, boolean>; - 'task.fail': Listener, Error, boolean>; - 'task.pass': Listener, any, boolean>; - 'task.skip': Listener, any, boolean>; - tasks: Listener[], any>; - 'tasks.parallel': Listener[], any>; -} - export interface ConsoleState { disabled: boolean; final: boolean; @@ -53,7 +33,7 @@ export interface ConsoleState { stopped: boolean; } -export default class Console extends Emitter { +export default class Console { bufferedStreams: (() => void)[] = []; debug: Debugger; @@ -62,6 +42,20 @@ export default class Console extends Emitter { logs: string[] = []; + onError: Event<[Error]>; + + onRoutine: Event<[Routine, unknown, boolean]>; + + onRoutines: Event<[Routine[], unknown, boolean]>; + + onStart: Event; + + onStop: Event<[Error | null]>; + + onTask: Event<[Task, unknown, boolean]>; + + onTasks: Event<[Task[], unknown, boolean]>; + outputQueue: Output[] = []; tool: Tool; @@ -78,12 +72,18 @@ export default class Console extends Emitter { private writers: typeof BOUND_WRITERS; constructor(tool: Tool, /* test only */ testWriters: typeof BOUND_WRITERS = BOUND_WRITERS) { - super(); - this.debug = tool.createDebugger('console'); this.tool = tool; this.writers = testWriters; + this.onError = new Event('error'); + this.onRoutine = new Event('routine'); + this.onRoutines = new Event('routines'); + this.onStart = new Event('start'); + this.onStop = new Event('stop'); + this.onTask = new Event('task'); + this.onTasks = new Event('task'); + // istanbul ignore next if (process.env.NODE_ENV !== 'test') { process @@ -344,7 +344,7 @@ export default class Console extends Emitter { this.err(`\n${this.errorLogs.join('\n')}\n`); } - this.emit('error', [error]); + this.onError.emit([error]); } else { if (this.logs.length > 0) { this.out(`\n${this.logs.join('\n')}\n`); @@ -391,7 +391,7 @@ export default class Console extends Emitter { } this.debug('Starting console render loop'); - this.emit('start', args); + this.onStart.emit(args); this.wrapStreams(); this.displayHeader(); this.state.started = true; @@ -431,7 +431,7 @@ export default class Console extends Emitter { this.renderFinalOutput(error); this.unwrapStreams(); - this.emit('stop', [error]); + this.onStop.emit([error]); this.state.stopped = true; this.state.started = false; } diff --git a/packages/core/src/Executor.ts b/packages/core/src/Executor.ts index 0216669fc..fbddd99c5 100644 --- a/packages/core/src/Executor.ts +++ b/packages/core/src/Executor.ts @@ -57,59 +57,25 @@ export default abstract class Executor { * Execute a routine with the provided value. */ executeRoutine = async (routine: Routine, value?: T): Promise => { - const { console: cli } = this.tool; - let result = null; + this.tool.console.onRoutine.emit([routine, value, this.parallel]); - cli.emit('routine', [routine, value, this.parallel]); - - try { - result = await routine.run(this.context, value); - - if (routine.isSkipped()) { - cli.emit('routine.skip', [routine, value, this.parallel]); - } else { - cli.emit('routine.pass', [routine, result, this.parallel]); - } - } catch (error) { - cli.emit('routine.fail', [routine, error, this.parallel]); - - throw error; - } - - return result; + return routine.run(this.context, value); }; /** * Execute a task with the provided value. */ executeTask = async (task: Task, value?: T): Promise => { - const { console: cli } = this.tool; - let result = null; - - cli.emit('task', [task, value, this.parallel]); - - try { - result = await task.run(this.context, value); - - if (task.isSkipped()) { - cli.emit('task.skip', [task, value, this.parallel]); - } else { - cli.emit('task.pass', [task, result, this.parallel]); - } - } catch (error) { - cli.emit('task.fail', [task, error, this.parallel]); - - throw error; - } + this.tool.console.onTask.emit([task, value, this.parallel]); - return result; + return task.run(this.context, value); }; /** * Run all routines with the defined executor. */ runRoutines(routines: Routine[], value?: T): Promise { - this.tool.console.emit(this.parallel ? 'routines.parallel' : 'routines', [routines, value]); + this.tool.console.onRoutines.emit([routines, value, this.parallel]); return this.run(this.executeRoutine as any, routines, value); } @@ -118,7 +84,7 @@ export default abstract class Executor { * Run all tasks with the defined executor. */ runTasks(tasks: Task[], value?: T): Promise { - this.tool.console.emit(this.parallel ? 'tasks.parallel' : 'tasks', [tasks, value]); + this.tool.console.onTasks.emit([tasks, value, this.parallel]); return this.run(this.executeTask, tasks, value); } diff --git a/packages/core/src/Reporter.ts b/packages/core/src/Reporter.ts index 9d90b1680..c2b55a160 100644 --- a/packages/core/src/Reporter.ts +++ b/packages/core/src/Reporter.ts @@ -39,7 +39,8 @@ export default class Reporter extends Plugin = (...args: A) => R; +export type Listener = A extends [infer A1, infer A2, infer A3] + ? (a1: A1, a2: A2, a3: A3) => R + : A extends [infer A1, infer A2] + ? (a1: A1, a2: A2) => R + : A extends [infer A1] + ? (a1: A1) => R + : A extends unknown[] + ? (...args: A) => R + : never; export type Scope = string; diff --git a/packages/reporter-nyan/src/NyanReporter.ts b/packages/reporter-nyan/src/NyanReporter.ts index 9d85fed6d..cdcd61000 100644 --- a/packages/reporter-nyan/src/NyanReporter.ts +++ b/packages/reporter-nyan/src/NyanReporter.ts @@ -30,13 +30,10 @@ export default class NyanReporter extends Reporter { this.rainbowWidth = this.size().columns - this.catWidth * 2; this.rainbowColors = this.generateColors(); - this.console - .on('start', this.handleStart) - .on('stop', this.handleStop) - .on('routine', this.handleRoutine) - .on('routine.pass', this.handleRoutine) - .on('routine.fail', this.handleRoutine) - .on('task', this.handleTask); + this.console.onStart.listen(this.handleStart); + this.console.onStop.listen(this.handleStop); + this.console.onRoutine.listen(this.handleRoutine); + this.console.onTask.listen(this.handleTask); } handleStart = () => { @@ -50,7 +47,13 @@ export default class NyanReporter extends Reporter { }; handleRoutine = (routine: Routine) => { - this.activeRoutine = routine; + const handler = () => { + this.activeRoutine = routine; + }; + + routine.onFail.listen(handler); + routine.onPass.listen(handler); + handler(); }; handleTask = (task: Task) => { From 3a11a07c55a0995f263126ea1c0966e4d3776b67 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Wed, 27 Mar 2019 23:04:33 -0700 Subject: [PATCH 12/23] More updates. --- packages/core/src/Console.ts | 35 +++++++++++++++++++++++++++++++++ packages/event/README.md | 2 +- packages/event/package.json | 2 +- packages/event/src/BaseEvent.ts | 4 ++++ 4 files changed, 41 insertions(+), 2 deletions(-) diff --git a/packages/core/src/Console.ts b/packages/core/src/Console.ts index 124e6ea99..d7a64a33e 100644 --- a/packages/core/src/Console.ts +++ b/packages/core/src/Console.ts @@ -511,4 +511,39 @@ export default class Console { WRAPPED_STREAMS[name] = true; }); } + + /** + * Temporary bridge until v2. + */ + on(eventName: string, listener: any) { + switch (eventName) { + case 'error': + this.onError.listen(listener); + break; + case 'routine': + this.onRoutine.listen(listener); + break; + case 'routines': + case 'routines.parallel': + this.onRoutines.listen(listener); + break; + case 'start': + this.onStart.listen(listener); + break; + case 'stop': + this.onStop.listen(listener); + break; + case 'task': + this.onTask.listen(listener); + break; + case 'tasks': + case 'tasks.parallel': + this.onTasks.listen(listener); + break; + default: + throw new Error(`Unsupported event ${eventName}.`); + } + + return this; + } } diff --git a/packages/event/README.md b/packages/event/README.md index f5989e8a6..a98610e15 100644 --- a/packages/event/README.md +++ b/packages/event/README.md @@ -1,5 +1,5 @@ # Event [![Build Status](https://travis-ci.org/milesj/boost.svg?branch=master)](https://travis-ci.org/milesj/boost) -[![npm version](https://badge.fury.io/js/%40boost%2Femitter.svg)](https://www.npmjs.com/package/@boost/event) +[![npm version](https://badge.fury.io/js/%40boost%event.svg)](https://www.npmjs.com/package/@boost/event) [![npm deps](https://david-dm.org/milesj/boost.svg?path=packages/event)](https://www.npmjs.com/package/@boost/event) diff --git a/packages/event/package.json b/packages/event/package.json index 9fe25eb84..e986bea26 100644 --- a/packages/event/package.json +++ b/packages/event/package.json @@ -1,7 +1,7 @@ { "name": "@boost/event", "version": "0.0.0", - "description": "A type-safe event emitter. Designed for Boost applications.", + "description": "A type-safe event system. Designed for Boost applications.", "keywords": [], "main": "./lib/index.js", "types": "./lib/index.d.ts", diff --git a/packages/event/src/BaseEvent.ts b/packages/event/src/BaseEvent.ts index 63967ebf5..bd1b988c6 100644 --- a/packages/event/src/BaseEvent.ts +++ b/packages/event/src/BaseEvent.ts @@ -90,6 +90,10 @@ export default abstract class BaseEvent { * Validate the name/scope match a defined pattern. */ protected validateName(name: string, type: string): string { + if (type === 'scope' && name === '*') { + return name; + } + if (!name.match(EVENT_NAME_PATTERN)) { throw new Error( `Invalid event ${type} "${name}". May only contain dashes, periods, and lowercase characters.`, From 46e8aba59acb9b09fb2adcb2fa003c3ae9271d69 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Thu, 28 Mar 2019 18:39:03 -0700 Subject: [PATCH 13/23] Bring back old emitter. --- packages/core/src/Console.ts | 48 +-- packages/core/src/Emitter.ts | 92 ++++++ packages/core/src/Executor.ts | 4 + packages/core/src/Routine.ts | 18 -- packages/core/src/Task.ts | 33 +- packages/core/src/Tool.ts | 21 +- packages/core/src/constants.ts | 1 + packages/core/src/index.ts | 4 +- packages/core/tests/Emitter.test.ts | 220 ++++++++++++++ packages/event/tests/Emitter.test.ts | 431 --------------------------- scripts/build-packages.sh | 13 +- 11 files changed, 346 insertions(+), 539 deletions(-) create mode 100644 packages/core/src/Emitter.ts create mode 100644 packages/core/tests/Emitter.test.ts delete mode 100644 packages/event/tests/Emitter.test.ts diff --git a/packages/core/src/Console.ts b/packages/core/src/Console.ts index d7a64a33e..097b71768 100644 --- a/packages/core/src/Console.ts +++ b/packages/core/src/Console.ts @@ -4,11 +4,13 @@ import exit from 'exit'; import cliSize from 'term-size'; import ansiEscapes from 'ansi-escapes'; import { Event } from '@boost/event'; +import Emitter from './Emitter'; +import Task from './Task'; import Tool from './Tool'; import Output from './Output'; +import Routine from './Routine'; import SignalError from './SignalError'; import { Debugger } from './types'; -import { Routine, Task } from '.'; // 8 FPS (60 FPS is actually too fast as it tears) export const FPS_RATE = 125; @@ -33,7 +35,7 @@ export interface ConsoleState { stopped: boolean; } -export default class Console { +export default class Console extends Emitter { bufferedStreams: (() => void)[] = []; debug: Debugger; @@ -72,6 +74,8 @@ export default class Console { private writers: typeof BOUND_WRITERS; constructor(tool: Tool, /* test only */ testWriters: typeof BOUND_WRITERS = BOUND_WRITERS) { + super(); + this.debug = tool.createDebugger('console'); this.tool = tool; this.writers = testWriters; @@ -82,7 +86,7 @@ export default class Console { this.onStart = new Event('start'); this.onStop = new Event('stop'); this.onTask = new Event('task'); - this.onTasks = new Event('task'); + this.onTasks = new Event('tasks'); // istanbul ignore next if (process.env.NODE_ENV !== 'test') { @@ -344,6 +348,7 @@ export default class Console { this.err(`\n${this.errorLogs.join('\n')}\n`); } + this.emit('error', [error]); this.onError.emit([error]); } else { if (this.logs.length > 0) { @@ -391,6 +396,7 @@ export default class Console { } this.debug('Starting console render loop'); + this.emit('start', args); this.onStart.emit(args); this.wrapStreams(); this.displayHeader(); @@ -431,6 +437,7 @@ export default class Console { this.renderFinalOutput(error); this.unwrapStreams(); + this.emit('stop', [error]); this.onStop.emit([error]); this.state.stopped = true; this.state.started = false; @@ -511,39 +518,4 @@ export default class Console { WRAPPED_STREAMS[name] = true; }); } - - /** - * Temporary bridge until v2. - */ - on(eventName: string, listener: any) { - switch (eventName) { - case 'error': - this.onError.listen(listener); - break; - case 'routine': - this.onRoutine.listen(listener); - break; - case 'routines': - case 'routines.parallel': - this.onRoutines.listen(listener); - break; - case 'start': - this.onStart.listen(listener); - break; - case 'stop': - this.onStop.listen(listener); - break; - case 'task': - this.onTask.listen(listener); - break; - case 'tasks': - case 'tasks.parallel': - this.onTasks.listen(listener); - break; - default: - throw new Error(`Unsupported event ${eventName}.`); - } - - return this; - } } diff --git a/packages/core/src/Emitter.ts b/packages/core/src/Emitter.ts new file mode 100644 index 000000000..cc06545d7 --- /dev/null +++ b/packages/core/src/Emitter.ts @@ -0,0 +1,92 @@ +import camelCase from 'lodash/camelCase'; +import upperFirst from 'lodash/upperFirst'; +import { EVENT_NAME_PATTERN } from './constants'; + +export type EventArguments = any[]; + +export type EventListener = (...args: EventArguments) => false | void; + +export default class Emitter { + listeners: Map> = new Map(); + + /** + * Remove all listeners for the defined event name. + */ + clearListeners(eventName: string): this { + this.getListeners(eventName).clear(); + + return this; + } + + /** + * Syncronously execute listeners for the defined event and arguments. + */ + emit(eventName: string, args: EventArguments = []): this { + Array.from(this.getListeners(eventName)).some(listener => listener(...args) === false); + + return this; + } + + /** + * Return all event names with registered listeners. + */ + getEventNames(): string[] { + return Array.from(this.listeners.keys()); + } + + /** + * Return a set of listeners for a specific event name. + */ + getListeners(eventName: string): Set { + if (!eventName.match(EVENT_NAME_PATTERN)) { + throw new Error( + `Invalid event name "${eventName}". ` + + 'May only contain dashes, periods, and lowercase characters.', + ); + } + + if (!this.listeners.has(eventName)) { + this.listeners.set(eventName, new Set()); + } + + return this.listeners.get(eventName)!; + } + + /** + * Remove a listener function from a specific event name. + */ + off(eventName: string, listener: EventListener): this { + this.getListeners(eventName).delete(listener); + + return this; + } + + /** + * Register a listener function to a specific event name. + */ + on(eventName: string, listener: EventListener): this { + if (typeof listener !== 'function') { + throw new TypeError(`Invalid event listener for "${eventName}", must be a function.`); + } + + let eventProp = eventName; + const args = ['listener']; + + if (eventName.includes('.')) { + const [scope, event] = eventName.split('.', 2); + + args.push(`'${scope}'`); + eventProp = event; + } + + console.warn( + `Boost emitter has been deprecated. Please migrate \`on('${eventName}', listener)\` to the new event system, \`on${upperFirst( + camelCase(eventProp), + )}.listen(${args.join(', ')})\`.'`, + ); + + this.getListeners(eventName).add(listener); + + return this; + } +} diff --git a/packages/core/src/Executor.ts b/packages/core/src/Executor.ts index fbddd99c5..254961551 100644 --- a/packages/core/src/Executor.ts +++ b/packages/core/src/Executor.ts @@ -57,6 +57,7 @@ export default abstract class Executor { * Execute a routine with the provided value. */ executeRoutine = async (routine: Routine, value?: T): Promise => { + this.tool.console.emit('routine', [routine, value, this.parallel]); this.tool.console.onRoutine.emit([routine, value, this.parallel]); return routine.run(this.context, value); @@ -66,6 +67,7 @@ export default abstract class Executor { * Execute a task with the provided value. */ executeTask = async (task: Task, value?: T): Promise => { + this.tool.console.emit('task', [task, value, this.parallel]); this.tool.console.onTask.emit([task, value, this.parallel]); return task.run(this.context, value); @@ -75,6 +77,7 @@ export default abstract class Executor { * Run all routines with the defined executor. */ runRoutines(routines: Routine[], value?: T): Promise { + this.tool.console.emit(this.parallel ? 'routines.parallel' : 'routines', [routines, value]); this.tool.console.onRoutines.emit([routines, value, this.parallel]); return this.run(this.executeRoutine as any, routines, value); @@ -84,6 +87,7 @@ export default abstract class Executor { * Run all tasks with the defined executor. */ runTasks(tasks: Task[], value?: T): Promise { + this.tool.console.emit(this.parallel ? 'tasks.parallel' : 'tasks', [tasks, value]); this.tool.console.onTasks.emit([tasks, value, this.parallel]); return this.run(this.executeTask, tasks, value); diff --git a/packages/core/src/Routine.ts b/packages/core/src/Routine.ts index 156fe4a1e..012c8cd73 100644 --- a/packages/core/src/Routine.ts +++ b/packages/core/src/Routine.ts @@ -265,24 +265,6 @@ export default abstract class Routine< return task; } - /** - * Temporary bridge until v2. - */ - on(eventName: string, listener: any) { - switch (eventName) { - case 'command': - this.onCommand.listen(listener); - break; - case 'command.data': - this.onCommandData.listen(listener); - break; - default: - throw new Error(`Unsupported event ${eventName}.`); - } - - return this; - } - /** * Execute the current routine and return a new value. * This method *must* be overridden in a subclass. diff --git a/packages/core/src/Task.ts b/packages/core/src/Task.ts index 4f36ad305..0f23e140f 100644 --- a/packages/core/src/Task.ts +++ b/packages/core/src/Task.ts @@ -1,4 +1,5 @@ import { Event, BailEvent } from '@boost/event'; +import Emitter from './Emitter'; import Context from './Context'; import { STATUS_PENDING, @@ -22,7 +23,7 @@ export interface TaskMetadata { stopTime: number; } -export default class Task { +export default class Task extends Emitter { action: TaskAction; // @ts-ignore Set after instantiation @@ -54,6 +55,8 @@ export default class Task { statusText: string = ''; constructor(title: string, action: TaskAction) { + super(); + if (!title || typeof title !== 'string') { throw new Error('Tasks require a title.'); } @@ -118,11 +121,13 @@ export default class Task { */ async run(context: Ctx, value: any): Promise { this.setContext(context); + this.emit('run', [value]); const skip = this.onRun.emit([value]); if (skip || this.isSkipped()) { this.status = STATUS_SKIPPED; + this.emit('skip', [value]); this.onSkip.emit([value]); return Promise.resolve(value); @@ -135,10 +140,12 @@ export default class Task { this.output = await this.action(context, value, this); this.status = STATUS_PASSED; this.metadata.stopTime = Date.now(); + this.emit('pass', [this.output]); this.onPass.emit([this.output]); } catch (error) { this.status = STATUS_FAILED; this.metadata.stopTime = Date.now(); + this.emit('fail', [error]); this.onFail.emit([error]); throw error; @@ -168,28 +175,4 @@ export default class Task { return this; } - - /** - * Temporary bridge until v2. - */ - on(eventName: string, listener: any) { - switch (eventName) { - case 'fail': - this.onFail.listen(listener); - break; - case 'pass': - this.onPass.listen(listener); - break; - case 'run': - this.onRun.listen(listener); - break; - case 'skip': - this.onSkip.listen(listener); - break; - default: - throw new Error(`Unsupported event ${eventName}.`); - } - - return this; - } } diff --git a/packages/core/src/Tool.ts b/packages/core/src/Tool.ts index 73a17127d..cd49030cf 100644 --- a/packages/core/src/Tool.ts +++ b/packages/core/src/Tool.ts @@ -15,6 +15,7 @@ import parseArgs, { Arguments, Options as ArgOptions } from 'yargs-parser'; import { Event } from '@boost/event'; import ConfigLoader from './ConfigLoader'; import Console from './Console'; +import Emitter from './Emitter'; import ExitError from './ExitError'; import ModuleLoader from './ModuleLoader'; import Plugin from './Plugin'; @@ -73,7 +74,7 @@ export interface ToolPluginRegistry { export default class Tool< PluginRegistry extends ToolPluginRegistry, Config extends ToolConfig = ToolConfig -> { +> extends Emitter { args?: Arguments; argv: string[] = []; @@ -102,6 +103,8 @@ export default class Tool< private translator: Translator | null = null; constructor(options: ToolOptions, argv: string[] = []) { + super(); + this.argv = argv; this.options = optimal( options, @@ -161,6 +164,7 @@ export default class Tool< // Cleanup when an exit occurs process.on('exit', code => { + this.emit('exit', [code]); this.onExit.emit([code]); }); } @@ -586,21 +590,6 @@ export default class Tool< return this; } - /** - * Temporary bridge until v2. - */ - on(eventName: string, listener: any) { - switch (eventName) { - case 'exit': - this.onExit.listen(listener); - break; - default: - throw new Error(`Unsupported event ${eventName}.`); - } - - return this; - } - /** * Load the package.json and local configuration files. * diff --git a/packages/core/src/constants.ts b/packages/core/src/constants.ts index 808cf1ce4..04647cf71 100644 --- a/packages/core/src/constants.ts +++ b/packages/core/src/constants.ts @@ -2,6 +2,7 @@ import { Status } from './types'; export const APP_NAME_PATTERN: RegExp = /^[a-z]{1}[-a-z0-9]+[a-z]{1}$/u; export const CONFIG_NAME_PATTERN: RegExp = /^[a-z]{1}[a-zA-Z0-9]+$/u; +export const EVENT_NAME_PATTERN: RegExp = /^[-a-z.]+$/u; export const MODULE_NAME_PATTERN: RegExp = /^(@[-a-z]+\/)?[-a-z]+$/u; export const PLUGIN_NAME_PATTERN: RegExp = /^([a-z]+):[-a-z]+$/u; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 8a83e6eee..032d6f913 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -3,11 +3,11 @@ * @license https://opensource.org/licenses/MIT */ -import Emitter from '@boost/event'; import CLI from './CLI'; import ConfigLoader from './ConfigLoader'; import Console from './Console'; import Context from './Context'; +import Emitter, { EventArguments, EventListener } from './Emitter'; import ExitError from './ExitError'; import SignalError from './SignalError'; import { AggregatedResponse } from './Executor'; @@ -28,6 +28,8 @@ export { Console, Context, Emitter, + EventArguments, + EventListener, ExitError, ModuleLoader, Output, diff --git a/packages/core/tests/Emitter.test.ts b/packages/core/tests/Emitter.test.ts new file mode 100644 index 000000000..f88d2f2dc --- /dev/null +++ b/packages/core/tests/Emitter.test.ts @@ -0,0 +1,220 @@ +import Emitter from '../src/Emitter'; + +describe('Emitter', () => { + let emitter: Emitter; + + beforeEach(() => { + emitter = new Emitter(); + }); + + describe('clearListeners()', () => { + it('deletes all listeners', () => { + emitter.on('foo', () => {}); + emitter.on('foo', () => {}); + + expect(emitter.getListeners('foo').size).toBe(2); + + emitter.clearListeners('foo'); + + expect(emitter.getListeners('foo').size).toBe(0); + }); + }); + + describe('emit()', () => { + it('executes listeners in order', () => { + let output = ''; + + emitter.on('foo', () => { + output += 'A'; + }); + emitter.on('foo', () => { + output += 'B'; + }); + emitter.on('foo', () => { + output += 'C'; + }); + emitter.emit('foo'); + + expect(output).toBe('ABC'); + }); + + it('executes listeners syncronously with arguments', () => { + const output: number[] = []; + + emitter.on('foo', (a, b) => { + output.push(a, b * 2); + }); + emitter.on('foo', (a, b) => { + output.push(a * 3, b * 4); + }); + emitter.on('foo', (a, b) => { + output.push(a * 5, b * 6); + }); + emitter.emit('foo', [2, 3]); + + expect(output).toEqual([2, 6, 6, 12, 10, 18]); + }); + + it('executes listeners syncronously while passing values to each', () => { + let value = 'foo'; + + emitter.on('foo', () => { + value = value.toUpperCase(); + }); + emitter.on('foo', () => { + value = value + .split('') + .reverse() + .join(''); + }); + emitter.on('foo', () => { + value = `${value}-${value}`; + }); + + emitter.emit('foo'); + + expect(value).toBe('OOF-OOF'); + }); + + it('executes listeners syncronously with arguments while passing values to each', () => { + const value: string[] = []; + + emitter.on('foo', a => { + value.push(a.repeat(3)); + }); + emitter.on('foo', (a, b) => { + value.push(b.repeat(2)); + }); + emitter.on('foo', (a, b, c) => { + value.push(c.repeat(1)); + }); + + emitter.emit('foo', ['foo', 'bar', 'baz']); + + expect(value).toEqual(['foofoofoo', 'barbar', 'baz']); + }); + + it('execution can be stopped', () => { + let count = 0; + + emitter.on('foo', () => { + count += 1; + }); + emitter.on('foo', () => false); + emitter.on('foo', () => { + count += 1; + }); + emitter.emit('foo'); + + expect(count).toBe(1); + }); + + it('passes arguments to listeners', () => { + const baseArgs = [1, 2, 3]; + let args; + + emitter.on('foo', (...eventArgs) => { + args = eventArgs; + }); + emitter.emit('foo', baseArgs); + + expect(args).toEqual(baseArgs); + }); + + it('passes value by modifying event object', () => { + let value = 0; + + emitter.on('foo', () => { + value += 1; + }); + emitter.on('foo', () => { + value += 1; + }); + emitter.on('foo', () => { + value += 1; + }); + + emitter.emit('foo'); + + expect(value).toBe(3); + }); + + describe('with namespace', () => { + it('executes listeners in order', () => { + let output = ''; + + emitter.on('ns.foo', () => { + output += 'A'; + }); + emitter.on('ns.foo', () => { + output += 'B'; + }); + emitter.on('ns.foo', () => { + output += 'C'; + }); + emitter.emit('ns.foo'); + + expect(output).toBe('ABC'); + }); + }); + }); + + describe('getEventNames()', () => { + it('returns an array of event names', () => { + emitter.getListeners('foo'); + emitter.getListeners('bar'); + emitter.getListeners('baz'); + emitter.getListeners('ns.qux'); + + expect(emitter.getEventNames()).toEqual(['foo', 'bar', 'baz', 'ns.qux']); + }); + }); + + describe('getListeners()', () => { + it('errors if name contains invalid characters', () => { + expect(() => emitter.getListeners('foo+bar')).toThrowErrorMatchingSnapshot(); + }); + + it('creates the listeners set if it does not exist', () => { + expect(emitter.listeners.has('foo')).toBe(false); + + const set = emitter.getListeners('foo'); + + expect(set).toBeInstanceOf(Set); + expect(emitter.listeners.has('foo')).toBe(true); + }); + }); + + describe('off()', () => { + it('removes a listener from the set', () => { + const listener = () => {}; + + emitter.on('foo', listener); + + expect(emitter.getListeners('foo').has(listener)).toBe(true); + + emitter.off('foo', listener); + + expect(emitter.getListeners('foo').has(listener)).toBe(false); + }); + }); + + describe('on()', () => { + it('errors if listener is not a function', () => { + expect(() => { + // @ts-ignore + emitter.on('foo', 123); + }).toThrowErrorMatchingSnapshot(); + }); + + it('adds a listener to the set', () => { + const listener = () => {}; + + expect(emitter.getListeners('foo').has(listener)).toBe(false); + + emitter.on('foo', listener); + + expect(emitter.getListeners('foo').has(listener)).toBe(true); + }); + }); +}); diff --git a/packages/event/tests/Emitter.test.ts b/packages/event/tests/Emitter.test.ts deleted file mode 100644 index bb3632f9d..000000000 --- a/packages/event/tests/Emitter.test.ts +++ /dev/null @@ -1,431 +0,0 @@ -import Emitter from '../src/Emitter'; -import { BaseListener, Listener, ParallelListener, WaterfallListener } from '../src/types'; - -describe('Emitter', () => { - let emitter: Emitter<{ - foo: Listener; - bar: Listener; - baz: Listener; - qux: BaseListener; - 'ns.one': Listener; - 'ns.two': Listener; - parallel: ParallelListener; - waterfall: WaterfallListener; - 'waterfall.array': WaterfallListener; - 'waterfall.object': WaterfallListener<{ [key: string]: string }>; - }>; - - beforeEach(() => { - emitter = new Emitter(); - }); - - it('passes arguments to listeners', () => { - const baseArgs: [number, number] = [1, 2]; - let args; - - emitter.on('foo', (...eventArgs: any[]) => { - args = eventArgs; - }); - - emitter.emit('foo', baseArgs); - - expect(args).toEqual(baseArgs); - }); - - describe('clearListeners()', () => { - it('deletes all listeners', () => { - emitter.on('foo', () => {}); - emitter.on('foo', () => {}); - - expect(emitter.getListeners('foo').size).toBe(2); - - emitter.clearListeners('foo'); - - expect(emitter.getListeners('foo').size).toBe(0); - }); - }); - - describe('emit()', () => { - it('executes listeners in order', () => { - let output = ''; - - emitter.on('foo', () => { - output += 'A'; - }); - emitter.on('foo', () => { - output += 'B'; - }); - emitter.on('foo', () => { - output += 'C'; - }); - emitter.emit('foo', [0, 0]); - - expect(output).toBe('ABC'); - }); - - it('executes listeners synchronously with arguments', () => { - const output: number[] = []; - - emitter.on('foo', (a: number, b: number) => { - output.push(a, b * 2); - }); - emitter.on('foo', (a: number, b: number) => { - output.push(a * 3, b * 4); - }); - emitter.on('foo', (a: number, b: number) => { - output.push(a * 5, b * 6); - }); - emitter.emit('foo', [2, 3]); - - expect(output).toEqual([2, 6, 6, 12, 10, 18]); - }); - - it('executes listeners synchronously while passing values to each', () => { - let value = 'foo'; - - emitter.on('baz', () => { - value = value.toUpperCase(); - }); - emitter.on('baz', () => { - value = value - .split('') - .reverse() - .join(''); - }); - emitter.on('baz', () => { - value = `${value}-${value}`; - }); - - emitter.emit('baz', []); - - expect(value).toBe('OOF-OOF'); - }); - - it('executes listeners synchronously with arguments while passing values to each', () => { - const value: string[] = []; - - emitter.on('bar', (a: string) => { - value.push(a.repeat(3)); - }); - emitter.on('bar', (a: string, b: string) => { - value.push(b.repeat(2)); - }); - emitter.on('bar', (a: string, b: string, c: string) => { - value.push(c.repeat(1)); - }); - - emitter.emit('bar', ['foo', 'bar', 'baz']); - - expect(value).toEqual(['foofoofoo', 'barbar', 'baz']); - }); - - it('execution can be not be stopped (bailed)', () => { - let count = 0; - - emitter.on('baz', () => { - count += 1; - }); - emitter.on('baz', () => false); - emitter.on('baz', () => { - count += 1; - }); - emitter.on('baz', () => { - count += 1; - }); - - emitter.emit('baz', []); - - expect(count).toBe(3); - }); - }); - - describe('emitBail()', () => { - it('executes listeners in order', () => { - let output = ''; - - emitter.on('foo', () => { - output += 'A'; - }); - emitter.on('foo', () => { - output += 'B'; - }); - emitter.on('foo', () => { - output += 'C'; - }); - - emitter.emitBail('foo', [0, 0]); - - expect(output).toBe('ABC'); - }); - - it('executes listeners synchronously with arguments', () => { - const output: number[] = []; - - emitter.on('foo', (a: number, b: number) => { - output.push(a, b * 2); - }); - emitter.on('foo', (a: number, b: number) => { - output.push(a * 3, b * 4); - }); - emitter.on('foo', (a: number, b: number) => { - output.push(a * 5, b * 6); - }); - - emitter.emitBail('foo', [2, 3]); - - expect(output).toEqual([2, 6, 6, 12, 10, 18]); - }); - - it('executes listeners synchronously while passing values to each', () => { - let value = 'foo'; - - emitter.on('baz', () => { - value = value.toUpperCase(); - }); - emitter.on('baz', () => { - value = value - .split('') - .reverse() - .join(''); - }); - emitter.on('baz', () => { - value = `${value}-${value}`; - }); - - emitter.emitBail('baz', []); - - expect(value).toBe('OOF-OOF'); - }); - - it('executes listeners synchronously with arguments while passing values to each', () => { - const value: string[] = []; - - emitter.on('bar', (a: string) => { - value.push(a.repeat(3)); - }); - emitter.on('bar', (a: string, b: string) => { - value.push(b.repeat(2)); - }); - emitter.on('bar', (a: string, b: string, c: string) => { - value.push(c.repeat(1)); - }); - - emitter.emitBail('bar', ['foo', 'bar', 'baz']); - - expect(value).toEqual(['foofoofoo', 'barbar', 'baz']); - }); - - it('execution can be stopped', () => { - let count = 0; - - emitter.on('baz', () => { - count += 1; - }); - emitter.on('baz', () => false); - emitter.on('baz', () => { - count += 1; - }); - emitter.on('baz', () => { - count += 1; - }); - - emitter.emitBail('baz', []); - - expect(count).toBe(1); - }); - }); - - describe('emitParallel()', () => { - beforeEach(() => { - jest.useRealTimers(); - }); - - afterEach(() => { - jest.useFakeTimers(); - }); - - it('returns a promise', () => { - expect(emitter.emitParallel('parallel', [0])).toBeInstanceOf(Promise); - }); - - it('executes listeners asynchronously with arguments', async () => { - const output: number[] = []; - - function getRandom() { - return Math.round(Math.random() * (500 - 0) + 0); - } - - emitter.on( - 'parallel', - (value: number) => - new Promise(resolve => { - setTimeout(() => { - resolve(value * 2); - }, getRandom()); - }), - ); - emitter.on( - 'parallel', - (value: number) => - new Promise(resolve => { - setTimeout(() => { - resolve(value * 3); - }, getRandom()); - }), - ); - emitter.on( - 'parallel', - (value: number) => - new Promise(resolve => { - setTimeout(() => { - resolve(value * 4); - }, getRandom()); - }), - ); - - await emitter.emitParallel('parallel', [1]); - - expect(output).not.toEqual([2, 3, 4]); - }); - }); - - describe('emitWaterfall()', () => { - it('executes listeners in order with the value being passed to each function', () => { - emitter.on('waterfall', (value: string) => `${value}B`); - emitter.on('waterfall', (value: string) => `${value}C`); - emitter.on('waterfall', (value: string) => `${value}D`); - - const output = emitter.emitWaterfall('waterfall', 'A'); - - expect(output).toBe('ABCD'); - }); - - it('supports arrays', () => { - emitter.on('waterfall.array', (value: string[]) => [...value, 'B']); - emitter.on('waterfall.array', (value: string[]) => [...value, 'C']); - emitter.on('waterfall.array', (value: string[]) => [...value, 'D']); - - const output = emitter.emitWaterfall('waterfall.array', ['A']); - - expect(output).toEqual(['A', 'B', 'C', 'D']); - }); - - it('supports objects', () => { - emitter.on('waterfall.object', (value: { [key: string]: string }) => ({ ...value, B: 'B' })); - emitter.on('waterfall.object', (value: { [key: string]: string }) => ({ ...value, C: 'C' })); - emitter.on('waterfall.object', (value: { [key: string]: string }) => ({ ...value, D: 'D' })); - - const output = emitter.emitWaterfall('waterfall.object', { A: 'A' }); - - expect(output).toEqual({ - A: 'A', - B: 'B', - C: 'C', - D: 'D', - }); - }); - }); - - describe('getEventNames()', () => { - it('returns an array of event names', () => { - emitter.getListeners('foo'); - emitter.getListeners('bar'); - emitter.getListeners('baz'); - emitter.getListeners('ns.two'); - - expect(emitter.getEventNames()).toEqual(['foo', 'bar', 'baz', 'ns.two']); - }); - }); - - describe('getListeners()', () => { - it('errors if name contains invalid characters', () => { - expect(() => { - // @ts-ignore Allow invalid name - emitter.getListeners('foo+bar'); - }).toThrowErrorMatchingSnapshot(); - }); - - it('creates the listeners set if it does not exist', () => { - expect(emitter.listeners.foo).toBeUndefined(); - - const set = emitter.getListeners('foo'); - - expect(set).toBeInstanceOf(Set); - expect(emitter.listeners.foo).toBeUndefined(); - }); - }); - - describe('off()', () => { - it('removes a listener from the set', () => { - const listener = () => {}; - - emitter.on('foo', listener); - - expect(emitter.getListeners('foo').has(listener)).toBe(true); - - emitter.off('foo', listener); - - expect(emitter.getListeners('foo').has(listener)).toBe(false); - }); - }); - - describe('on()', () => { - it('errors if listener is not a function', () => { - expect(() => { - // @ts-ignore Allow invalid type - emitter.on('foo', 123); - }).toThrowErrorMatchingSnapshot(); - }); - - it('adds a listener to the set', () => { - const listener = () => {}; - - expect(emitter.getListeners('foo').has(listener)).toBe(false); - - emitter.on('foo', listener); - - expect(emitter.getListeners('foo').has(listener)).toBe(true); - }); - }); - - describe('once()', () => { - it('errors if listener is not a function', () => { - expect(() => { - // @ts-ignore Allow invalid type - emitter.once('foo', 123); - }).toThrowErrorMatchingSnapshot(); - }); - - it('adds a listener to the set', () => { - const listener = () => {}; - - expect(emitter.getListeners('foo').has(listener)).toBe(false); - expect(emitter.getListeners('foo').size).toBe(0); - - emitter.once('foo', listener); - - // Gets wrapped - expect(emitter.getListeners('foo').has(listener)).toBe(false); - expect(emitter.getListeners('foo').size).toBe(1); - }); - - it('only executes once and removes the listener', () => { - let count = 0; - - const listener = () => { - count += 1; - }; - - emitter.once('baz', listener); - - expect(emitter.getListeners('baz').size).toBe(1); - - emitter.emit('baz', []); - emitter.emit('baz', []); - emitter.emit('baz', []); - - expect(count).toBe(1); - expect(emitter.getListeners('baz').size).toBe(0); - }); - }); -}); diff --git a/scripts/build-packages.sh b/scripts/build-packages.sh index c27d2308b..fa089623a 100644 --- a/scripts/build-packages.sh +++ b/scripts/build-packages.sh @@ -12,14 +12,7 @@ build_pkg() { node ../../node_modules/.bin/tsc } +build_pkg "./packages/event" build_pkg "./packages/core" -cd "$root" || exit - -REGEX="/(core|theme)" - -for pkg in ./packages/*; do - if ! [[ "$pkg" =~ $REGEX ]] - then - build_pkg "$pkg" - fi -done +build_pkg "./packages/reporter-nyan" +build_pkg "./packages/test-utils" From 5644c6860e8bbca1e2d833412b27be30688420a1 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 1 Apr 2019 21:15:53 -0700 Subject: [PATCH 14/23] Add example types. --- package.json | 5 ++ .../core/tests/reporters/CIReporter.test.ts | 6 +- packages/event/src/Event.ts | 4 +- packages/event/tests/typings.ts | 85 +++++++++---------- 4 files changed, 51 insertions(+), 49 deletions(-) diff --git a/package.json b/package.json index 1eeef7759..02de7cd6d 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,11 @@ ], "settings": { "node": true + }, + "typescript": { + "exclude": [ + "tests/typings.ts" + ] } } } diff --git a/packages/core/tests/reporters/CIReporter.test.ts b/packages/core/tests/reporters/CIReporter.test.ts index fa5c4bb7a..1c718c7ba 100644 --- a/packages/core/tests/reporters/CIReporter.test.ts +++ b/packages/core/tests/reporters/CIReporter.test.ts @@ -1,5 +1,5 @@ import chalk from 'chalk'; -import { mockTool, mockConsole } from '../../src/testUtils'; +import { mockTool, mockConsole, mockRoutine } from '../../src/testUtils'; import CIReporter from '../../src/reporters/CIReporter'; describe('CIReporter', () => { @@ -53,13 +53,13 @@ describe('CIReporter', () => { it('increments count', () => { expect(reporter.routineCount).toBe(0); - reporter.handleRoutine(); + reporter.handleRoutine(mockRoutine(reporter.tool)); expect(reporter.routineCount).toBe(1); }); it('logs a period', () => { - reporter.handleRoutine(); + reporter.handleRoutine(mockRoutine(reporter.tool)); expect(outSpy).toHaveBeenCalledWith('.'); }); diff --git a/packages/event/src/Event.ts b/packages/event/src/Event.ts index 86b537013..d049a2890 100644 --- a/packages/event/src/Event.ts +++ b/packages/event/src/Event.ts @@ -5,11 +5,9 @@ export default class Event extends BaseEvent /** * Synchronously execute listeners with the defined arguments. */ - emit(args: Args, scope?: Scope): this { + emit(args: Args, scope?: Scope) { Array.from(this.getListeners(scope)).forEach(listener => { listener(...args); }); - - return this; } } diff --git a/packages/event/tests/typings.ts b/packages/event/tests/typings.ts index a86eadd0b..2008e2f79 100644 --- a/packages/event/tests/typings.ts +++ b/packages/event/tests/typings.ts @@ -1,54 +1,53 @@ -import Emitter from '../src/Emitter'; -import { Listener, WaterfallListener, ParallelListener } from '../src/types'; +import { Event, BailEvent, ParallelEvent, WaterfallEvent } from '../src'; -const emitter = new Emitter<{ - args: Listener; - 'no.args': Listener; - parallel: ParallelListener; - waterfall: WaterfallListener; -}>(); +const foo = new Event<[number, string?]>('foo'); +const bar = new BailEvent<[number, number, object]>('bar'); +const baz = new ParallelEvent('baz'); +const qux = new WaterfallEvent('qux'); // VALID - -emitter.on('args', () => {}); -emitter.on('args', (num, bool, str) => {}); -emitter.on('no.args', () => false); -emitter.on('parallel', () => Promise.resolve()); -emitter.on('waterfall', str => str); - -emitter.emit('args', [123, true, 'abc']); -emitter.emitBail('no.args', []); -emitter.emitParallel('parallel', [{ test: true }, ['a', 'b', 'c']]); -emitter.emitWaterfall('waterfall', 'abc'); +foo.listen(() => {}); +foo.listen((num, str) => {}); +foo.emit([123]); +foo.emit([123, 'abc']); // INVALID +foo.listen((num, str, bool) => {}); +foo.emit([true]); +foo.emit([123, 456]); +foo.emit([123, 'abc', true]); -// Unknown event name -emitter.on('unknown.name', () => {}); - -// Extra arg -emitter.on('args', (num, bool, str, other) => {}); - -// Return not boolean or void -emitter.on('no.args', () => ({})); - -// Return not promise -emitter.on('parallel', () => {}); - -// Missing args -emitter.emit('args', [123]); +// VALID +bar.listen(() => {}); +bar.listen((num, str) => true); +bar.listen((num, str, obj) => false); +bar.emit([123, 456, {}]); -// Invalid arg type -emitter.emit('args', [123, {}, 'abc']); +// INVALID +bar.listen(() => 123); +bar.emit([true]); +bar.emit([123, 'abc']); +bar.emit([123, 456, 'abc']); +const notBoolReturn: string = bar.emit([123, 456, {}]); -// Extra arg -emitter.emit('no.args', ['abc']); +// VALID +baz.listen(() => Promise.resolve()); +baz.listen((num, str) => Promise.reject()); +baz.emit([]); +baz.emit([123, 456, {}]); -// Empty args -emitter.emitParallel('parallel', []); +// INVALID +baz.listen(() => 123); +const notPromiseReturn: string = baz.emit(['abc']); -// Waterfall only 1 arg -emitter.emitWaterfall('waterfall', ['foo']); +// VALID +qux.listen(() => 'abc'); +qux.listen(str => str.toUpperCase()); +qux.emit('qux'); -// Args must be a string -emitter.once('waterfall', (value: number) => value); +// INVALID +qux.listen(() => 123); +qux.listen(() => {}); +qux.emit(123); +qux.emit(['qux']); +const notStringReturn: number = qux.emit('abc'); From 7abe843fd8920aa3847ec8f8a61542d86b9d2d17 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 1 Apr 2019 21:23:18 -0700 Subject: [PATCH 15/23] Add BailEvent tests. --- packages/event/tests/BailEvent.test.ts | 91 +++++++++++++++++++ .../tests/__snapshots__/Emitter.test.ts.snap | 7 -- 2 files changed, 91 insertions(+), 7 deletions(-) create mode 100644 packages/event/tests/BailEvent.test.ts delete mode 100644 packages/event/tests/__snapshots__/Emitter.test.ts.snap diff --git a/packages/event/tests/BailEvent.test.ts b/packages/event/tests/BailEvent.test.ts new file mode 100644 index 000000000..94e4a3a3b --- /dev/null +++ b/packages/event/tests/BailEvent.test.ts @@ -0,0 +1,91 @@ +import BailEvent from '../src/BailEvent'; + +describe('BailEvent', () => { + let event: BailEvent<[string, number]>; + + beforeEach(() => { + event = new BailEvent('bail.test'); + }); + + it('executes listeners in order', () => { + let output = ''; + + event.listen(value => { + output += value; + output += 'B'; + }); + event.listen(() => { + output += 'C'; + }); + event.listen(() => { + output += 'D'; + }); + + const result = event.emit(['A', 0]); + + expect(result).toBe(false); + expect(output).toBe('ABCD'); + }); + + it('executes listeners based on scope', () => { + let output = ''; + + event.listen(() => { + output += 'A'; + }, 'foo'); + event.listen(() => { + output += 'B'; + }, 'bar'); + event.listen(() => { + output += 'C'; + }, 'baz'); + + const result = event.emit(['A', 0], 'bar'); + + expect(result).toBe(false); + expect(output).toBe('B'); + }); + + it('bails the loop if a listener returns false', () => { + let count = 0; + + event.listen(() => { + count += 1; + }); + event.listen(() => { + count += 1; + + return false; + }); + event.listen(() => { + count += 1; + }); + + const result = event.emit(['', 0]); + + expect(result).toBe(true); + expect(count).toBe(2); + }); + + it('doesnt bail the loop if a listener returns true', () => { + let count = 0; + + event.listen((string, number) => { + count += number; + count += 1; + }); + event.listen(() => { + count += 1; + + return true; + }); + event.listen(() => { + count += 1; + }); + + const result = event.emit(['', 1]); + + expect(result).toBe(false); + expect(count).toBe(4); + }); +}); diff --git a/packages/event/tests/__snapshots__/Emitter.test.ts.snap b/packages/event/tests/__snapshots__/Emitter.test.ts.snap deleted file mode 100644 index b1a13c889..000000000 --- a/packages/event/tests/__snapshots__/Emitter.test.ts.snap +++ /dev/null @@ -1,7 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Emitter getListeners() errors if name contains invalid characters 1`] = `"Invalid event name \\"foo+bar\\". May only contain dashes, periods, and lowercase characters."`; - -exports[`Emitter on() errors if listener is not a function 1`] = `"Invalid event listener for \\"foo\\", must be a function."`; - -exports[`Emitter once() errors if listener is not a function 1`] = `"Invalid event listener for \\"foo\\", must be a function."`; From 0e831246a23f460f91118836aa9b7afa2b8b1d8f Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 1 Apr 2019 21:38:30 -0700 Subject: [PATCH 16/23] Add more tests. --- packages/event/src/BaseEvent.ts | 6 +- packages/event/src/constants.ts | 5 +- packages/event/tests/BaseEvent.test.ts | 183 ++++++++++++++++++ packages/event/tests/Event.test.ts | 48 +++++ .../__snapshots__/BaseEvent.test.ts.snap | 9 + 5 files changed, 246 insertions(+), 5 deletions(-) create mode 100644 packages/event/tests/BaseEvent.test.ts create mode 100644 packages/event/tests/Event.test.ts create mode 100644 packages/event/tests/__snapshots__/BaseEvent.test.ts.snap diff --git a/packages/event/src/BaseEvent.ts b/packages/event/src/BaseEvent.ts index bd1b988c6..6d50bb781 100644 --- a/packages/event/src/BaseEvent.ts +++ b/packages/event/src/BaseEvent.ts @@ -1,4 +1,4 @@ -import { EVENT_NAME_PATTERN } from './constants'; +import { EVENT_NAME_PATTERN, DEFAULT_SCOPE } from './constants'; import { Listener, Scope } from './types'; export default abstract class BaseEvent { @@ -26,7 +26,7 @@ export default abstract class BaseEvent { /** * Return a set of listeners for a specific event scope. */ - getListeners(scope: Scope = '*'): Set> { + getListeners(scope: Scope = DEFAULT_SCOPE): Set> { const key = this.validateName(scope, 'scope'); if (!this.listeners.has(key)) { @@ -90,7 +90,7 @@ export default abstract class BaseEvent { * Validate the name/scope match a defined pattern. */ protected validateName(name: string, type: string): string { - if (type === 'scope' && name === '*') { + if (type === 'scope' && name === DEFAULT_SCOPE) { return name; } diff --git a/packages/event/src/constants.ts b/packages/event/src/constants.ts index 93e8914f6..5622d1d99 100644 --- a/packages/event/src/constants.ts +++ b/packages/event/src/constants.ts @@ -1,2 +1,3 @@ -// eslint-disable-next-line import/prefer-default-export -export const EVENT_NAME_PATTERN: RegExp = /^[a-z]{1}[-.a-z0-9]*[a-z]{1}$/u; +export const DEFAULT_SCOPE = '*'; + +export const EVENT_NAME_PATTERN = /^[a-z]{1}[-.a-z0-9]*[a-z]{1}$/u; diff --git a/packages/event/tests/BaseEvent.test.ts b/packages/event/tests/BaseEvent.test.ts new file mode 100644 index 000000000..7d2f8fe63 --- /dev/null +++ b/packages/event/tests/BaseEvent.test.ts @@ -0,0 +1,183 @@ +import Event from '../src/Event'; + +describe('Event', () => { + let event: Event; + + beforeEach(() => { + event = new Event('event.test'); + }); + + describe('constructor()', () => { + it('errors if name contains invalid characters', () => { + expect(() => new Event('foo+bar')).toThrowErrorMatchingSnapshot(); + }); + }); + + describe('clearListeners()', () => { + it('deletes all listeners in a scope', () => { + event.listen(() => {}, 'foo'); + event.listen(() => {}, 'bar'); + event.listen(() => {}, 'baz'); + + expect(event.getListeners('foo').size).toBe(1); + expect(event.getListeners('bar').size).toBe(1); + expect(event.getListeners('baz').size).toBe(1); + + event.clearListeners('foo'); + + expect(event.getListeners('foo').size).toBe(0); + expect(event.getListeners('bar').size).toBe(1); + expect(event.getListeners('baz').size).toBe(1); + }); + + it('deletes all listeners across all scopes', () => { + event.listen(() => {}, 'foo'); + event.listen(() => {}, 'bar'); + event.listen(() => {}, 'baz'); + + expect(event.getListeners('foo').size).toBe(1); + expect(event.getListeners('bar').size).toBe(1); + expect(event.getListeners('baz').size).toBe(1); + + event.clearListeners(); + + expect(event.getListeners('foo').size).toBe(0); + expect(event.getListeners('bar').size).toBe(0); + expect(event.getListeners('baz').size).toBe(0); + }); + }); + + describe('getListeners()', () => { + it('errors if scope contains invalid characters', () => { + expect(() => event.getListeners('foo+bar')).toThrowErrorMatchingSnapshot(); + }); + + it('doesnt error for default scope', () => { + expect(() => event.getListeners()).not.toThrowError(); + }); + + it('creates the listeners set if it does not exist', () => { + expect(event.listeners.has('foo')).toBe(false); + + const set = event.getListeners('foo'); + + expect(set).toBeInstanceOf(Set); + expect(event.listeners.has('foo')).toBe(true); + }); + }); + + describe('getScopes()', () => { + it('returns an array of scope names', () => { + event.getListeners(); + event.getListeners('foo'); + event.getListeners('bar'); + event.getListeners('baz'); + + expect(event.getScopes()).toEqual(['*', 'foo', 'bar', 'baz']); + }); + }); + + describe('listen()', () => { + it('errors if listener is not a function', () => { + expect(() => { + // @ts-ignore Check invalid type + event.listen(123); + }).toThrowErrorMatchingSnapshot(); + }); + + it('adds a listener to the default scope', () => { + const listener = () => {}; + + expect(event.getListeners().has(listener)).toBe(false); + + event.listen(listener); + + expect(event.getListeners().has(listener)).toBe(true); + }); + + it('adds a listener to a specific scope', () => { + const listener = () => {}; + + expect(event.getListeners('foo').has(listener)).toBe(false); + + event.listen(listener, 'foo'); + + expect(event.getListeners('foo').has(listener)).toBe(true); + }); + }); + + describe('unlisten()', () => { + it('removes a listener from the default scope', () => { + const listener = () => {}; + + event.listen(listener); + + expect(event.getListeners().has(listener)).toBe(true); + + event.unlisten(listener); + + expect(event.getListeners().has(listener)).toBe(false); + }); + + it('removes a listener from a specific scope', () => { + const listener = () => {}; + + event.listen(listener, 'foo'); + + expect(event.getListeners('foo').has(listener)).toBe(true); + + event.unlisten(listener, 'foo'); + + expect(event.getListeners('foo').has(listener)).toBe(false); + }); + }); + + describe('once()', () => { + it('errors if listener is not a function', () => { + expect(() => { + // @ts-ignore Check invalid type + event.once(123); + }).toThrowErrorMatchingSnapshot(); + }); + + it('adds a listener to the default scope', () => { + const listener = () => {}; + + expect(event.getListeners().size).toBe(0); + + event.once(listener); + + expect(event.getListeners().has(listener)).toBe(false); + expect(event.getListeners().size).toBe(1); + }); + + it('adds a listener to a specific scope', () => { + const listener = () => {}; + + expect(event.getListeners('foo').size).toBe(0); + + event.once(listener, 'foo'); + + expect(event.getListeners('foo').has(listener)).toBe(false); + expect(event.getListeners('foo').size).toBe(1); + }); + + it('removes the listener once executed', () => { + let count = 0; + const listener = () => { + count += 1; + }; + + event.once(listener); + + expect(event.getListeners().size).toBe(1); + + event.emit([]); + event.emit([]); + event.emit([]); + + expect(event.getListeners().size).toBe(0); + expect(count).toBe(1); + }); + }); +}); diff --git a/packages/event/tests/Event.test.ts b/packages/event/tests/Event.test.ts new file mode 100644 index 000000000..0a2fe9c1b --- /dev/null +++ b/packages/event/tests/Event.test.ts @@ -0,0 +1,48 @@ +import Event from '../src/Event'; + +describe('Event', () => { + let event: Event<[string, string, string]>; + + beforeEach(() => { + event = new Event('event.test'); + }); + + it('executes listeners synchronously while passing values to each', () => { + let value = 'foo'; + + event.listen(() => { + value = value.toUpperCase(); + }); + event.listen(() => { + value = value + .split('') + .reverse() + .join(''); + }); + event.listen(() => { + value = `${value}-${value}`; + }); + + event.emit(['', '', '']); + + expect(value).toBe('OOF-OOF'); + }); + + it('executes listeners synchronously with arguments', () => { + const value: string[] = []; + + event.listen(a => { + value.push(a.repeat(3)); + }); + event.listen((a, b) => { + value.push(b.repeat(2)); + }); + event.listen((a, b, c) => { + value.push(c.repeat(1)); + }); + + event.emit(['foo', 'bar', 'baz']); + + expect(value).toEqual(['foofoofoo', 'barbar', 'baz']); + }); +}); diff --git a/packages/event/tests/__snapshots__/BaseEvent.test.ts.snap b/packages/event/tests/__snapshots__/BaseEvent.test.ts.snap new file mode 100644 index 000000000..b16063067 --- /dev/null +++ b/packages/event/tests/__snapshots__/BaseEvent.test.ts.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Event constructor() errors if name contains invalid characters 1`] = `"Invalid event name \\"foo+bar\\". May only contain dashes, periods, and lowercase characters."`; + +exports[`Event getListeners() errors if scope contains invalid characters 1`] = `"Invalid event scope \\"foo+bar\\". May only contain dashes, periods, and lowercase characters."`; + +exports[`Event listen() errors if listener is not a function 1`] = `"Invalid event listener for \\"event.test\\", must be a function."`; + +exports[`Event once() errors if listener is not a function 1`] = `"Invalid event listener for \\"event.test\\", must be a function."`; From 0bdcd6824374366f9a8efd29a7a071359faba227 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 1 Apr 2019 21:48:02 -0700 Subject: [PATCH 17/23] Add final tests. --- packages/event/tests/ParallelEvent.test.ts | 58 +++++++++++++++++++++ packages/event/tests/WaterfallEvent.test.ts | 44 ++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 packages/event/tests/ParallelEvent.test.ts create mode 100644 packages/event/tests/WaterfallEvent.test.ts diff --git a/packages/event/tests/ParallelEvent.test.ts b/packages/event/tests/ParallelEvent.test.ts new file mode 100644 index 000000000..0190e3ed1 --- /dev/null +++ b/packages/event/tests/ParallelEvent.test.ts @@ -0,0 +1,58 @@ +import ParallelEvent from '../src/ParallelEvent'; + +describe('ParallelEvent', () => { + let event: ParallelEvent<[number]>; + + beforeEach(() => { + event = new ParallelEvent('parallel.test'); + }); + + beforeEach(() => { + jest.useRealTimers(); + }); + + afterEach(() => { + jest.useFakeTimers(); + }); + + it('returns a promise', () => { + expect(event.emit([0])).toBeInstanceOf(Promise); + }); + + it('executes listeners asynchronously with arguments', async () => { + const output: number[] = []; + + function getRandom() { + return Math.round(Math.random() * (500 - 0) + 0); + } + + event.listen( + value => + new Promise(resolve => { + setTimeout(() => { + resolve(value * 2); + }, getRandom()); + }), + ); + event.listen( + value => + new Promise(resolve => { + setTimeout(() => { + resolve(value * 3); + }, getRandom()); + }), + ); + event.listen( + value => + new Promise(resolve => { + setTimeout(() => { + resolve(value * 4); + }, getRandom()); + }), + ); + + await event.emit([1]); + + expect(output).not.toEqual([2, 3, 4]); + }); +}); diff --git a/packages/event/tests/WaterfallEvent.test.ts b/packages/event/tests/WaterfallEvent.test.ts new file mode 100644 index 000000000..7ca60ae29 --- /dev/null +++ b/packages/event/tests/WaterfallEvent.test.ts @@ -0,0 +1,44 @@ +import WaterfallEvent from '../src/WaterfallEvent'; + +describe('WaterfallEvent', () => { + it('executes listeners in order with the value being passed to each function', () => { + const event = new WaterfallEvent('waterfall.test'); + + event.listen(value => `${value}B`); + event.listen(value => `${value}C`); + event.listen(value => `${value}D`); + + const output = event.emit('A'); + + expect(output).toBe('ABCD'); + }); + + it('supports arrays', () => { + const event = new WaterfallEvent('waterfall.array.test'); + + event.listen(value => [...value, 'B']); + event.listen(value => [...value, 'C']); + event.listen(value => [...value, 'D']); + + const output = event.emit(['A']); + + expect(output).toEqual(['A', 'B', 'C', 'D']); + }); + + it('supports objects', () => { + const event = new WaterfallEvent<{ [key: string]: string }>('waterfall.array.test'); + + event.listen(value => ({ ...value, B: 'B' })); + event.listen(value => ({ ...value, C: 'C' })); + event.listen(value => ({ ...value, D: 'D' })); + + const output = event.emit({ A: 'A' }); + + expect(output).toEqual({ + A: 'A', + B: 'B', + C: 'C', + D: 'D', + }); + }); +}); From 8a7d990d30e04985d6880ae95b9307032abd06f4 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 1 Apr 2019 22:06:46 -0700 Subject: [PATCH 18/23] Fix some tests. --- packages/core/src/Emitter.ts | 2 +- packages/core/src/reporters/CIReporter.ts | 2 +- packages/core/tests/Executor.test.ts | 24 +++++++------------ packages/core/tests/Reporter.test.ts | 7 +++--- packages/core/tests/Routine.test.ts | 15 ++++-------- .../tests/reporters/BoostReporter.test.ts | 10 ++++---- .../core/tests/reporters/CIReporter.test.ts | 13 +++++----- .../tests/reporters/ErrorReporter.test.ts | 4 ++-- packages/event/src/index.ts | 2 ++ packages/event/tests/typings.ts | 2 ++ .../reporter-nyan/tests/NyanReporter.test.ts | 15 ++++++------ 11 files changed, 43 insertions(+), 53 deletions(-) diff --git a/packages/core/src/Emitter.ts b/packages/core/src/Emitter.ts index cc06545d7..8c356e48e 100644 --- a/packages/core/src/Emitter.ts +++ b/packages/core/src/Emitter.ts @@ -80,7 +80,7 @@ export default class Emitter { } console.warn( - `Boost emitter has been deprecated. Please migrate \`on('${eventName}', listener)\` to the new event system, \`on${upperFirst( + `Boost emitter has been deprecated. Please migrate \`on('${eventName}', listener)\` to the new @boost/event system, \`on${upperFirst( camelCase(eventProp), )}.listen(${args.join(', ')})\`.'`, ); diff --git a/packages/core/src/reporters/CIReporter.ts b/packages/core/src/reporters/CIReporter.ts index 755dfb866..a42e8c0ae 100644 --- a/packages/core/src/reporters/CIReporter.ts +++ b/packages/core/src/reporters/CIReporter.ts @@ -11,8 +11,8 @@ export default class CIReporter extends Reporter { this.console.disable(); this.console.onStop.listen(this.handleStop); - this.console.onTask.listen(this.handleTask); this.console.onRoutine.listen(this.handleRoutine); + this.console.onTask.listen(this.handleTask); } handleRoutine = (routine: Routine) => { diff --git a/packages/core/tests/Executor.test.ts b/packages/core/tests/Executor.test.ts index 7ebbe8fc7..1bdcd8fdf 100644 --- a/packages/core/tests/Executor.test.ts +++ b/packages/core/tests/Executor.test.ts @@ -67,7 +67,7 @@ describe('Executor', () => { expect(routine.status).toBe(STATUS_FAILED); }); - it('emits console events when skipped', async () => { + it('emits console event `routine` when skipped', async () => { const spy = jest.fn(); executor.tool.console.emit = spy; @@ -77,10 +77,9 @@ describe('Executor', () => { await executor.executeRoutine(routine, 123); expect(spy).toHaveBeenCalledWith('routine', [routine, 123, false]); - expect(spy).toHaveBeenCalledWith('routine.skip', [routine, 123, false]); }); - it('emits console events if a success', async () => { + it('emits console event `routine` if a success', async () => { const spy = jest.fn(); executor.tool.console.emit = spy; @@ -88,10 +87,9 @@ describe('Executor', () => { await executor.executeRoutine(routine, 123); expect(spy).toHaveBeenCalledWith('routine', [routine, 123, false]); - expect(spy).toHaveBeenCalledWith('routine.pass', [routine, 123, false]); }); - it('emits console events if a failure', async () => { + it('emits console event `routine` if a failure', async () => { const spy = jest.fn(); executor.tool.console.emit = spy; @@ -108,10 +106,9 @@ describe('Executor', () => { } expect(spy).toHaveBeenCalledWith('routine', [routine, 123, false]); - expect(spy).toHaveBeenCalledWith('routine.fail', [routine, new Error('Oops'), false]); }); - it('emits console events with parallel flag', async () => { + it('emits console event `routine` with parallel flag', async () => { const spy = jest.fn(); executor.tool.console.emit = spy; @@ -120,7 +117,6 @@ describe('Executor', () => { await executor.executeRoutine(routine, 123); expect(spy).toHaveBeenCalledWith('routine', [routine, 123, true]); - expect(spy).toHaveBeenCalledWith('routine.pass', [routine, 123, true]); }); }); @@ -159,7 +155,7 @@ describe('Executor', () => { expect(task.status).toBe(STATUS_FAILED); }); - it('emits console events when skipped', async () => { + it('emits console event `task` when skipped', async () => { const spy = jest.fn(); executor.tool.console.emit = spy; @@ -169,10 +165,9 @@ describe('Executor', () => { await executor.executeTask(task, 123); expect(spy).toHaveBeenCalledWith('task', [task, 123, false]); - expect(spy).toHaveBeenCalledWith('task.skip', [task, 123, false]); }); - it('emits console events if a success', async () => { + it('emits console event `task` if a success', async () => { const spy = jest.fn(); executor.tool.console.emit = spy; @@ -180,10 +175,9 @@ describe('Executor', () => { await executor.executeTask(task, 123); expect(spy).toHaveBeenCalledWith('task', [task, 123, false]); - expect(spy).toHaveBeenCalledWith('task.pass', [task, 369, false]); }); - it('emits console events if a failure', async () => { + it('emits console event `task` if a failure', async () => { const spy = jest.fn(); executor.tool.console.emit = spy; @@ -199,10 +193,9 @@ describe('Executor', () => { } expect(spy).toHaveBeenCalledWith('task', [task, 123, false]); - expect(spy).toHaveBeenCalledWith('task.fail', [task, new Error('Oops'), false]); }); - it('emits console events with parallel flag', async () => { + it('emits console event `task` with parallel flag', async () => { const spy = jest.fn(); executor.tool.console.emit = spy; @@ -211,7 +204,6 @@ describe('Executor', () => { await executor.executeTask(task, 123); expect(spy).toHaveBeenCalledWith('task', [task, 123, true]); - expect(spy).toHaveBeenCalledWith('task.pass', [task, 369, true]); }); }); diff --git a/packages/core/tests/Reporter.test.ts b/packages/core/tests/Reporter.test.ts index 3d209a95b..84ad82da0 100644 --- a/packages/core/tests/Reporter.test.ts +++ b/packages/core/tests/Reporter.test.ts @@ -24,12 +24,13 @@ describe('Reporter', () => { describe('bootstrap()', () => { it('sets start and stop events', () => { - const spy = jest.spyOn(reporter.console, 'on'); + const startSpy = jest.spyOn(reporter.console.onStart, 'listen'); + const stopSpy = jest.spyOn(reporter.console.onStop, 'listen'); reporter.bootstrap(); - expect(spy).toHaveBeenCalledWith('start', expect.anything()); - expect(spy).toHaveBeenCalledWith('stop', expect.anything()); + expect(startSpy).toHaveBeenCalledWith(expect.anything()); + expect(stopSpy).toHaveBeenCalledWith(expect.anything()); }); }); diff --git a/packages/core/tests/Routine.test.ts b/packages/core/tests/Routine.test.ts index cb6bc7e26..c03e08db3 100644 --- a/packages/core/tests/Routine.test.ts +++ b/packages/core/tests/Routine.test.ts @@ -221,23 +221,16 @@ describe('Routine', () => { }); it('pipes stdout/stderr to handler', async () => { - const spy1 = jest.spyOn(routine, 'emit'); - const spy2 = jest.spyOn(routine.tool.console, 'emit'); + const commandSpy = jest.spyOn(routine.onCommand, 'emit'); + const commandDataSpy = jest.spyOn(routine.onCommandData, 'emit'); const task = new Task('title', () => {}); task.status = STATUS_RUNNING; await routine.executeCommand('yarn', ['-v'], { task }); - expect(spy1).toHaveBeenCalledWith('command', ['yarn']); - expect(spy1).toHaveBeenCalledWith('command.data', ['yarn', expect.anything()]); - - expect(spy2).toHaveBeenCalledWith('command', ['yarn', expect.anything()]); - expect(spy2).toHaveBeenCalledWith('command.data', [ - 'yarn', - expect.anything(), - expect.anything(), - ]); + expect(commandSpy).toHaveBeenCalledWith(['yarn']); + expect(commandDataSpy).toHaveBeenCalledWith(['yarn', expect.anything()]); }); it('sets `statusText` on task', async () => { diff --git a/packages/core/tests/reporters/BoostReporter.test.ts b/packages/core/tests/reporters/BoostReporter.test.ts index ccde0865e..c953bc437 100644 --- a/packages/core/tests/reporters/BoostReporter.test.ts +++ b/packages/core/tests/reporters/BoostReporter.test.ts @@ -54,11 +54,11 @@ describe('BoostReporter', () => { describe('bootstrap()', () => { it('binds events', () => { - const spy = jest.spyOn(reporter.console, 'on'); + const spy = jest.spyOn(reporter.console.onRoutine, 'listen'); reporter.bootstrap(); - expect(spy).toHaveBeenCalledWith('routine', expect.anything()); + expect(spy).toHaveBeenCalledWith(expect.anything()); }); }); @@ -87,7 +87,7 @@ describe('BoostReporter', () => { it('marks as final when `skip` event is emitted', () => { reporter.handleRoutine(parent); - parent.emit('skip'); + parent.onSkip.emit(['']); expect(reporter.console.outputQueue[0].isFinal()).toBe(true); }); @@ -95,7 +95,7 @@ describe('BoostReporter', () => { it('marks as final when `pass` event is emitted', () => { reporter.handleRoutine(parent); - parent.emit('pass'); + parent.onPass.emit(['']); expect(reporter.console.outputQueue[0].isFinal()).toBe(true); }); @@ -103,7 +103,7 @@ describe('BoostReporter', () => { it('marks as final when `fail` event is emitted', () => { reporter.handleRoutine(parent); - parent.emit('fail'); + parent.onFail.emit([null]); expect(reporter.console.outputQueue[0].isFinal()).toBe(true); }); diff --git a/packages/core/tests/reporters/CIReporter.test.ts b/packages/core/tests/reporters/CIReporter.test.ts index 1c718c7ba..1f42aed43 100644 --- a/packages/core/tests/reporters/CIReporter.test.ts +++ b/packages/core/tests/reporters/CIReporter.test.ts @@ -20,16 +20,15 @@ describe('CIReporter', () => { describe('bootstrap()', () => { it('binds events', () => { - const spy = jest.spyOn(reporter.console, 'on'); + const startSpy = jest.spyOn(reporter.console.onStart, 'listen'); + const routineSpy = jest.spyOn(reporter.console.onRoutine, 'listen'); + const taskSpy = jest.spyOn(reporter.console.onTask, 'listen'); reporter.bootstrap(); - expect(spy).toHaveBeenCalledWith('stop', expect.anything()); - expect(spy).toHaveBeenCalledWith('task', expect.anything()); - expect(spy).toHaveBeenCalledWith('routine', expect.anything()); - expect(spy).toHaveBeenCalledWith('routine.skip', expect.anything()); - expect(spy).toHaveBeenCalledWith('routine.pass', expect.anything()); - expect(spy).toHaveBeenCalledWith('routine.fail', expect.anything()); + expect(startSpy).toHaveBeenCalledWith(expect.anything()); + expect(routineSpy).toHaveBeenCalledWith(expect.anything()); + expect(taskSpy).toHaveBeenCalledWith(expect.anything()); }); }); diff --git a/packages/core/tests/reporters/ErrorReporter.test.ts b/packages/core/tests/reporters/ErrorReporter.test.ts index 6c2fea8e2..3cd9fe37b 100644 --- a/packages/core/tests/reporters/ErrorReporter.test.ts +++ b/packages/core/tests/reporters/ErrorReporter.test.ts @@ -14,11 +14,11 @@ describe('ErrorReporter', () => { describe('bootstrap()', () => { it('binds events', () => { - const spy = jest.spyOn(reporter.console, 'on'); + const errorSpy = jest.spyOn(reporter.console.onError, 'listen'); reporter.bootstrap(); - expect(spy).toHaveBeenCalledWith('error', expect.anything()); + expect(errorSpy).toHaveBeenCalledWith(expect.anything()); }); }); diff --git a/packages/event/src/index.ts b/packages/event/src/index.ts index 22cd8fdff..08e1da3c6 100644 --- a/packages/event/src/index.ts +++ b/packages/event/src/index.ts @@ -10,6 +10,8 @@ import ParallelEvent from './ParallelEvent'; import WaterfallEvent from './WaterfallEvent'; export * from './constants'; + +// eslint-disable-next-line import/export export * from './types'; export { BailEvent, BaseEvent, Event, ParallelEvent, WaterfallEvent }; diff --git a/packages/event/tests/typings.ts b/packages/event/tests/typings.ts index 2008e2f79..7d4edc70e 100644 --- a/packages/event/tests/typings.ts +++ b/packages/event/tests/typings.ts @@ -1,3 +1,5 @@ +/* eslint-disable */ + import { Event, BailEvent, ParallelEvent, WaterfallEvent } from '../src'; const foo = new Event<[number, string?]>('foo'); diff --git a/packages/reporter-nyan/tests/NyanReporter.test.ts b/packages/reporter-nyan/tests/NyanReporter.test.ts index 354dd9516..c05d79f13 100644 --- a/packages/reporter-nyan/tests/NyanReporter.test.ts +++ b/packages/reporter-nyan/tests/NyanReporter.test.ts @@ -16,16 +16,17 @@ describe('NyanReporter', () => { describe('bootstrap()', () => { it('binds events', () => { - const spy = jest.spyOn(reporter.console, 'on'); + const startSpy = jest.spyOn(reporter.console.onStart, 'listen'); + const stopSpy = jest.spyOn(reporter.console.onStop, 'listen'); + const routineSpy = jest.spyOn(reporter.console.onRoutine, 'listen'); + const taskSpy = jest.spyOn(reporter.console.onTask, 'listen'); reporter.bootstrap(); - expect(spy).toHaveBeenCalledWith('start', expect.anything()); - expect(spy).toHaveBeenCalledWith('stop', expect.anything()); - expect(spy).toHaveBeenCalledWith('routine', expect.anything()); - expect(spy).toHaveBeenCalledWith('routine.pass', expect.anything()); - expect(spy).toHaveBeenCalledWith('routine.fail', expect.anything()); - expect(spy).toHaveBeenCalledWith('task', expect.anything()); + expect(startSpy).toHaveBeenCalledWith(expect.anything()); + expect(stopSpy).toHaveBeenCalledWith(expect.anything()); + expect(routineSpy).toHaveBeenCalledWith(expect.anything()); + expect(taskSpy).toHaveBeenCalledWith(expect.anything()); }); it('generates rainbow data', () => { From 660b9c3145247460b4b82ccf29701536aa4c8c47 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Tue, 2 Apr 2019 21:22:19 -0700 Subject: [PATCH 19/23] Update deps. --- package.json | 2 +- packages/core/src/outputs/ProgressOutput.ts | 2 +- packages/reporter-nyan/src/NyanReporter.ts | 2 +- yarn.lock | 649 +++++++++++++------- 4 files changed, 423 insertions(+), 232 deletions(-) diff --git a/package.json b/package.json index 02de7cd6d..42e47bc5e 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "release": "lerna publish" }, "devDependencies": { - "@milesj/build-tools": "^0.34.0", + "@milesj/build-tools": "^0.37.2", "fs-extra": "^7.0.1", "lerna": "^3.13.1" }, diff --git a/packages/core/src/outputs/ProgressOutput.ts b/packages/core/src/outputs/ProgressOutput.ts index f307cbb9b..14976fdd4 100644 --- a/packages/core/src/outputs/ProgressOutput.ts +++ b/packages/core/src/outputs/ProgressOutput.ts @@ -70,7 +70,7 @@ export default class ProgressOutput extends Output { } // Compile our template - const progress = Math.min(Math.max(current / total, 0.0), 1.0); + const progress = Math.min(Math.max(current / total, 0), 1); const percent = Math.floor(progress * 100); const elapsed = Date.now() - this.startTime; const estimated = percent === 100 ? 0 : elapsed * (total / current - 1); diff --git a/packages/reporter-nyan/src/NyanReporter.ts b/packages/reporter-nyan/src/NyanReporter.ts index cdcd61000..44ebf96ae 100644 --- a/packages/reporter-nyan/src/NyanReporter.ts +++ b/packages/reporter-nyan/src/NyanReporter.ts @@ -76,7 +76,7 @@ export default class NyanReporter extends Reporter { for (let i = 0; i < 6 * 7; i += 1) { const pi3 = Math.floor(Math.PI / 3); - const n = i * (1.0 / 6); + const n = i * (1 / 6); const r = Math.floor(3 * Math.sin(n) + 3); const g = Math.floor(3 * Math.sin(n + 2 * pi3) + 3); const b = Math.floor(3 * Math.sin(n + 4 * pi3) + 3); diff --git a/yarn.lock b/yarn.lock index ef24eeefe..69f112f38 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,22 +2,22 @@ # yarn lockfile v1 -"@babel/cli@^7.2.3": - version "7.2.3" - resolved "https://registry.npmjs.org/@babel/cli/-/cli-7.2.3.tgz#1b262e42a3e959d28ab3d205ba2718e1923cfee6" - integrity sha512-bfna97nmJV6nDJhXNPeEfxyMjWnt6+IjUAaDPiYRTBlm8L41n8nvw6UAqUCbvpFfU246gHPxW7sfWwqtF4FcYA== +"@babel/cli@^7.4.3": + version "7.4.3" + resolved "https://registry.npmjs.org/@babel/cli/-/cli-7.4.3.tgz#353048551306ff42e5855b788b6ccd9477289774" + integrity sha512-cbC5H9iTDV9H7sMxK5rUm18UbdVPNTPqgdzmQAkOUP3YLysgDWLZaysVAfylK49rgTlzL01a6tXyq9rCb3yLhQ== dependencies: commander "^2.8.1" convert-source-map "^1.1.0" fs-readdir-recursive "^1.1.0" glob "^7.0.0" - lodash "^4.17.10" + lodash "^4.17.11" mkdirp "^0.5.1" output-file-sync "^2.0.0" slash "^2.0.0" source-map "^0.5.0" optionalDependencies: - chokidar "^2.0.3" + chokidar "^2.0.4" "@babel/code-frame@^7.0.0": version "7.0.0" @@ -26,7 +26,7 @@ dependencies: "@babel/highlight" "^7.0.0" -"@babel/core@^7.1.0", "@babel/core@^7.4.0": +"@babel/core@^7.1.0": version "7.4.0" resolved "https://registry.npmjs.org/@babel/core/-/core-7.4.0.tgz#248fd6874b7d755010bfe61f557461d4f446d9e9" integrity sha512-Dzl7U0/T69DFOTwqz/FJdnOSWS57NpjNfCwMKHABr589Lg8uX1RrlBIJ7L5Dubt/xkLsx0xH5EBFzlBVes1ayA== @@ -46,6 +46,26 @@ semver "^5.4.1" source-map "^0.5.0" +"@babel/core@^7.4.3": + version "7.4.3" + resolved "https://registry.npmjs.org/@babel/core/-/core-7.4.3.tgz#198d6d3af4567be3989550d97e068de94503074f" + integrity sha512-oDpASqKFlbspQfzAE7yaeTmdljSH2ADIvBlb0RwbStltTuWa0+7CCI1fYVINNv9saHPa1W7oaKeuNuKj+RQCvA== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/generator" "^7.4.0" + "@babel/helpers" "^7.4.3" + "@babel/parser" "^7.4.3" + "@babel/template" "^7.4.0" + "@babel/traverse" "^7.4.3" + "@babel/types" "^7.4.0" + convert-source-map "^1.1.0" + debug "^4.1.0" + json5 "^2.1.0" + lodash "^4.17.11" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + "@babel/generator@^7.0.0", "@babel/generator@^7.4.0": version "7.4.0" resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.4.0.tgz#c230e79589ae7a729fd4631b9ded4dc220418196" @@ -167,6 +187,18 @@ "@babel/types" "^7.2.2" lodash "^4.17.10" +"@babel/helper-module-transforms@^7.4.3": + version "7.4.3" + resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.4.3.tgz#b1e357a1c49e58a47211a6853abb8e2aaefeb064" + integrity sha512-H88T9IySZW25anu5uqyaC1DaQre7ofM+joZtAaO2F8NBdFfupH0SZ4gKjgSFVcvtx/aAirqA9L9Clio2heYbZA== + dependencies: + "@babel/helper-module-imports" "^7.0.0" + "@babel/helper-simple-access" "^7.1.0" + "@babel/helper-split-export-declaration" "^7.0.0" + "@babel/template" "^7.2.2" + "@babel/types" "^7.2.2" + lodash "^4.17.11" + "@babel/helper-optimise-call-expression@^7.0.0": version "7.0.0" resolved "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz#a2920c5702b073c15de51106200aa8cad20497d5" @@ -186,6 +218,13 @@ dependencies: lodash "^4.17.10" +"@babel/helper-regex@^7.4.3": + version "7.4.3" + resolved "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.4.3.tgz#9d6e5428bfd638ab53b37ae4ec8caf0477495147" + integrity sha512-hnoq5u96pLCfgjXuj8ZLX3QQ+6nAulS+zSgi6HulUwFbEruRAKwbGLU5OvXkE14L8XW6XsQEKsIDfgthKLRAyA== + dependencies: + lodash "^4.17.11" + "@babel/helper-remap-async-to-generator@^7.1.0": version "7.1.0" resolved "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.1.0.tgz#361d80821b6f38da75bd3f0785ece20a88c5fe7f" @@ -241,6 +280,15 @@ "@babel/traverse" "^7.4.0" "@babel/types" "^7.4.0" +"@babel/helpers@^7.4.3": + version "7.4.3" + resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.4.3.tgz#7b1d354363494b31cb9a2417ae86af32b7853a3b" + integrity sha512-BMh7X0oZqb36CfyhvtbSmcWc3GXocfxv3yNsAEuM0l+fAqSO22rQrUpijr3oE/10jCTrB6/0b9kzmG4VetCj8Q== + dependencies: + "@babel/template" "^7.4.0" + "@babel/traverse" "^7.4.3" + "@babel/types" "^7.4.0" + "@babel/highlight@^7.0.0": version "7.0.0" resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz#f710c38c8d458e6dd9a201afb637fcb781ce99e4" @@ -255,6 +303,11 @@ resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.4.0.tgz#6de669e73ac3a32c754280d0fef8fca6aad2c416" integrity sha512-ZmMhJfU/+SXXvy9ALjDZopa3T3EixQtQai89JRC48eM9OUwrxJjYjuM/0wmdl2AekytlzMVhPY8cYdLb13kpKQ== +"@babel/parser@^7.4.3": + version "7.4.3" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.4.3.tgz#eb3ac80f64aa101c907d4ce5406360fe75b7895b" + integrity sha512-gxpEUhTS1sGA63EGQGuA+WESPR/6tz6ng7tSHFCmaTJK/cGK8y37cBTspX+U2xCAue2IQVvF6Z0oigmjwD8YGQ== + "@babel/plugin-proposal-async-generator-functions@^7.2.0": version "7.2.0" resolved "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz#b289b306669dce4ad20b0252889a15768c9d417e" @@ -288,18 +341,18 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-syntax-json-strings" "^7.2.0" -"@babel/plugin-proposal-nullish-coalescing-operator@^7.2.0": - version "7.2.0" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.2.0.tgz#c3fda766187b2f2162657354407247a758ee9cf9" - integrity sha512-QXj/YjFuFJd68oDvoc1e8aqLr2wz7Kofzvp6Ekd/o7MWZl+nZ0/cpStxND+hlZ7DpRWAp7OmuyT2areZ2V3YUA== +"@babel/plugin-proposal-nullish-coalescing-operator@^7.4.3": + version "7.4.3" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.4.3.tgz#df9ff90a9e3b94eb426e56be6100a85ac61a2b9f" + integrity sha512-eYtYniPfQOPY8rpsR8YK5pioJHrFLXjXOeYRf0vr5sWSPJU13or3CxLFn+uIb6fhukbYNch+L2VDAlvME2DSAw== dependencies: "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.2.0" -"@babel/plugin-proposal-object-rest-spread@^7.4.0": - version "7.4.0" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.4.0.tgz#e4960575205eadf2a1ab4e0c79f9504d5b82a97f" - integrity sha512-uTNi8pPYyUH2eWHyYWWSYJKwKg34hhgl4/dbejEjL+64OhbHjTX7wEVWMQl82tEmdDsGeu77+s8HHLS627h6OQ== +"@babel/plugin-proposal-object-rest-spread@^7.4.3": + version "7.4.3" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.4.3.tgz#be27cd416eceeba84141305b93c282f5de23bbb4" + integrity sha512-xC//6DNSSHVjq8O2ge0dyYlhshsH4T7XdCVoxbi5HzLYWfsC5ooFlJjrXk8RcAT+hjHAK9UjBXdylzSoDK3t4g== dependencies: "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-syntax-object-rest-spread" "^7.2.0" @@ -430,10 +483,10 @@ "@babel/helper-plugin-utils" "^7.0.0" lodash "^4.17.11" -"@babel/plugin-transform-classes@^7.4.0": - version "7.4.0" - resolved "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.4.0.tgz#e3428d3c8a3d01f33b10c529b998ba1707043d4d" - integrity sha512-XGg1Mhbw4LDmrO9rSTNe+uI79tQPdGs0YASlxgweYRLZqo/EQktjaOV4tchL/UZbM0F+/94uOipmdNGoaGOEYg== +"@babel/plugin-transform-classes@^7.4.3": + version "7.4.3" + resolved "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.4.3.tgz#adc7a1137ab4287a555d429cc56ecde8f40c062c" + integrity sha512-PUaIKyFUDtG6jF5DUJOfkBdwAS/kFFV3XFk7Nn0a6vR7ZT8jYw5cGtIlat77wcnd0C6ViGqo/wyNf4ZHytF/nQ== dependencies: "@babel/helper-annotate-as-pure" "^7.0.0" "@babel/helper-define-map" "^7.4.0" @@ -451,21 +504,21 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-destructuring@^7.4.0": - version "7.4.0" - resolved "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.4.0.tgz#acbb9b2418d290107db333f4d6cd8aa6aea00343" - integrity sha512-HySkoatyYTY3ZwLI8GGvkRWCFrjAGXUHur5sMecmCIdIharnlcWWivOqDJI76vvmVZfzwb6G08NREsrY96RhGQ== +"@babel/plugin-transform-destructuring@^7.4.3": + version "7.4.3" + resolved "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.4.3.tgz#1a95f5ca2bf2f91ef0648d5de38a8d472da4350f" + integrity sha512-rVTLLZpydDFDyN4qnXdzwoVpk1oaXHIvPEOkOLyr88o7oHxVc/LyrnDx+amuBWGOwUb7D1s/uLsKBNTx08htZg== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-dotall-regex@^7.2.0": - version "7.2.0" - resolved "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.2.0.tgz#f0aabb93d120a8ac61e925ea0ba440812dbe0e49" - integrity sha512-sKxnyHfizweTgKZf7XsXu/CNupKhzijptfTM+bozonIuyVrLWVUvYjE2bhuSBML8VQeMxq4Mm63Q9qvcvUcciQ== +"@babel/plugin-transform-dotall-regex@^7.4.3": + version "7.4.3" + resolved "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.4.3.tgz#fceff1c16d00c53d32d980448606f812cd6d02bf" + integrity sha512-9Arc2I0AGynzXRR/oPdSALv3k0rM38IMFyto7kOCwb5F9sLUt2Ykdo3V9yUPR+Bgr4kb6bVEyLkPEiBhzcTeoA== dependencies: "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-regex" "^7.0.0" - regexpu-core "^4.1.3" + "@babel/helper-regex" "^7.4.3" + regexpu-core "^4.5.4" "@babel/plugin-transform-duplicate-keys@^7.2.0": version "7.2.0" @@ -482,17 +535,17 @@ "@babel/helper-builder-binary-assignment-operator-visitor" "^7.1.0" "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-for-of@^7.4.0": - version "7.4.0" - resolved "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.4.0.tgz#56c8c36677f5d4a16b80b12f7b768de064aaeb5f" - integrity sha512-vWdfCEYLlYSxbsKj5lGtzA49K3KANtb8qCPQ1em07txJzsBwY+cKJzBHizj5fl3CCx7vt+WPdgDLTHmydkbQSQ== +"@babel/plugin-transform-for-of@^7.4.3": + version "7.4.3" + resolved "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.4.3.tgz#c36ff40d893f2b8352202a2558824f70cd75e9fe" + integrity sha512-UselcZPwVWNSURnqcfpnxtMehrb8wjXYOimlYQPBnup/Zld426YzIhNEvuRsEWVHfESIECGrxoI6L5QqzuLH5Q== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-function-name@^7.2.0": - version "7.2.0" - resolved "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.2.0.tgz#f7930362829ff99a3174c39f0afcc024ef59731a" - integrity sha512-kWgksow9lHdvBC2Z4mxTsvc7YdY7w/V6B2vy9cTIPtLEE9NhwoWivaxdNM/S37elu5bqlLP/qOY906LukO9lkQ== +"@babel/plugin-transform-function-name@^7.4.3": + version "7.4.3" + resolved "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.4.3.tgz#130c27ec7fb4f0cba30e958989449e5ec8d22bbd" + integrity sha512-uT5J/3qI/8vACBR9I1GlAuU/JqBtWdfCrynuOkrWG6nCDieZd5przB1vfP59FRHBZQ9DC2IUfqr/xKqzOD5x0A== dependencies: "@babel/helper-function-name" "^7.1.0" "@babel/helper-plugin-utils" "^7.0.0" @@ -504,6 +557,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-transform-member-expression-literals@^7.2.0": + version "7.2.0" + resolved "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.2.0.tgz#fa10aa5c58a2cb6afcf2c9ffa8cb4d8b3d489a2d" + integrity sha512-HiU3zKkSU6scTidmnFJ0bMX8hz5ixC93b4MHMiYebmk2lUVNGOboPsqQvx5LzooihijUoLR/v7Nc1rbBtnc7FA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-transform-modules-amd@^7.2.0": version "7.2.0" resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.2.0.tgz#82a9bce45b95441f617a24011dc89d12da7f4ee6" @@ -512,12 +572,12 @@ "@babel/helper-module-transforms" "^7.1.0" "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-modules-commonjs@^7.4.0": - version "7.4.0" - resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.4.0.tgz#3b8ec61714d3b75d20c5ccfa157f2c2e087fd4ca" - integrity sha512-iWKAooAkipG7g1IY0eah7SumzfnIT3WNhT4uYB2kIsvHnNSB6MDYVa5qyICSwaTBDBY2c4SnJ3JtEa6ltJd6Jw== +"@babel/plugin-transform-modules-commonjs@^7.4.3": + version "7.4.3" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.4.3.tgz#3917f260463ac08f8896aa5bd54403f6e1fed165" + integrity sha512-sMP4JqOTbMJMimqsSZwYWsMjppD+KRyDIUVW91pd7td0dZKAvPmhCaxhOzkzLParKwgQc7bdL9UNv+rpJB0HfA== dependencies: - "@babel/helper-module-transforms" "^7.1.0" + "@babel/helper-module-transforms" "^7.4.3" "@babel/helper-plugin-utils" "^7.0.0" "@babel/helper-simple-access" "^7.1.0" @@ -559,15 +619,22 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/helper-replace-supers" "^7.1.0" -"@babel/plugin-transform-parameters@^7.4.0": - version "7.4.0" - resolved "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.4.0.tgz#a1309426fac4eecd2a9439a4c8c35124a11a48a9" - integrity sha512-Xqv6d1X+doyiuCGDoVJFtlZx0onAX0tnc3dY8w71pv/O0dODAbusVv2Ale3cGOwfiyi895ivOBhYa9DhAM8dUA== +"@babel/plugin-transform-parameters@^7.4.3": + version "7.4.3" + resolved "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.4.3.tgz#e5ff62929fdf4cf93e58badb5e2430303003800d" + integrity sha512-ULJYC2Vnw96/zdotCZkMGr2QVfKpIT/4/K+xWWY0MbOJyMZuk660BGkr3bEKWQrrciwz6xpmft39nA4BF7hJuA== dependencies: "@babel/helper-call-delegate" "^7.4.0" "@babel/helper-get-function-arity" "^7.0.0" "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-transform-property-literals@^7.2.0": + version "7.2.0" + resolved "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.2.0.tgz#03e33f653f5b25c4eb572c98b9485055b389e905" + integrity sha512-9q7Dbk4RhgcLp8ebduOpCbtjh7C0itoLYHXd9ueASKAG/is5PQtMR5VJGka9NKqGhYEGn5ITahd4h9QeBMylWQ== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-transform-react-display-name@^7.0.0": version "7.2.0" resolved "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.2.0.tgz#ebfaed87834ce8dc4279609a4f0c324c156e3eb0" @@ -600,17 +667,24 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-syntax-jsx" "^7.2.0" -"@babel/plugin-transform-regenerator@^7.4.0": - version "7.4.0" - resolved "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.4.0.tgz#0780e27ee458cc3fdbad18294d703e972ae1f6d1" - integrity sha512-SZ+CgL4F0wm4npojPU6swo/cK4FcbLgxLd4cWpHaNXY/NJ2dpahODCqBbAwb2rDmVszVb3SSjnk9/vik3AYdBw== +"@babel/plugin-transform-regenerator@^7.4.3": + version "7.4.3" + resolved "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.4.3.tgz#2a697af96887e2bbf5d303ab0221d139de5e739c" + integrity sha512-kEzotPuOpv6/iSlHroCDydPkKYw7tiJGKlmYp6iJn4a6C/+b2FdttlJsLKYxolYHgotTJ5G5UY5h0qey5ka3+A== dependencies: regenerator-transform "^0.13.4" -"@babel/plugin-transform-runtime@^7.4.0": - version "7.4.0" - resolved "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.4.0.tgz#b4d8c925ed957471bc57e0b9da53408ebb1ed457" - integrity sha512-1uv2h9wnRj98XX3g0l4q+O3jFM6HfayKup7aIu4pnnlzGz0H+cYckGBC74FZIWJXJSXAmeJ9Yu5Gg2RQpS4hWg== +"@babel/plugin-transform-reserved-words@^7.2.0": + version "7.2.0" + resolved "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.2.0.tgz#4792af87c998a49367597d07fedf02636d2e1634" + integrity sha512-fz43fqW8E1tAB3DKF19/vxbpib1fuyCwSPE418ge5ZxILnBhWyhtPgz8eh1RCGGJlwvksHkyxMxh0eenFi+kFw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-runtime@^7.4.3": + version "7.4.3" + resolved "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.4.3.tgz#4d6691690ecdc9f5cb8c3ab170a1576c1f556371" + integrity sha512-7Q61bU+uEI7bCUFReT1NKn7/X6sDQsZ7wL1sJ9IYMAO7cI+eg6x9re1cEw2fCRMbbTVyoeUKWSV1M6azEfKCfg== dependencies: "@babel/helper-module-imports" "^7.0.0" "@babel/helper-plugin-utils" "^7.0.0" @@ -662,25 +736,25 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-syntax-typescript" "^7.2.0" -"@babel/plugin-transform-unicode-regex@^7.2.0": - version "7.2.0" - resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.2.0.tgz#4eb8db16f972f8abb5062c161b8b115546ade08b" - integrity sha512-m48Y0lMhrbXEJnVUaYly29jRXbQ3ksxPrS1Tg8t+MHqzXhtBYAvI51euOBaoAlZLPHsieY9XPVMf80a5x0cPcA== +"@babel/plugin-transform-unicode-regex@^7.4.3": + version "7.4.3" + resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.4.3.tgz#3868703fc0e8f443dda65654b298df576f7b863b" + integrity sha512-lnSNgkVjL8EMtnE8eSS7t2ku8qvKH3eqNf/IwIfnSPUqzgqYmRwzdsQWv4mNQAN9Nuo6Gz1Y0a4CSmdpu1Pp6g== dependencies: "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-regex" "^7.0.0" - regexpu-core "^4.1.3" + "@babel/helper-regex" "^7.4.3" + regexpu-core "^4.5.4" -"@babel/preset-env@^7.4.2": - version "7.4.2" - resolved "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.4.2.tgz#2f5ba1de2daefa9dcca653848f96c7ce2e406676" - integrity sha512-OEz6VOZaI9LW08CWVS3d9g/0jZA6YCn1gsKIy/fut7yZCJti5Lm1/Hi+uo/U+ODm7g4I6gULrCP+/+laT8xAsA== +"@babel/preset-env@^7.4.3": + version "7.4.3" + resolved "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.4.3.tgz#e71e16e123dc0fbf65a52cbcbcefd072fbd02880" + integrity sha512-FYbZdV12yHdJU5Z70cEg0f6lvtpZ8jFSDakTm7WXeJbLXh4R0ztGEu/SW7G1nJ2ZvKwDhz8YrbA84eYyprmGqw== dependencies: "@babel/helper-module-imports" "^7.0.0" "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-proposal-async-generator-functions" "^7.2.0" "@babel/plugin-proposal-json-strings" "^7.2.0" - "@babel/plugin-proposal-object-rest-spread" "^7.4.0" + "@babel/plugin-proposal-object-rest-spread" "^7.4.3" "@babel/plugin-proposal-optional-catch-binding" "^7.2.0" "@babel/plugin-proposal-unicode-property-regex" "^7.4.0" "@babel/plugin-syntax-async-generators" "^7.2.0" @@ -691,36 +765,39 @@ "@babel/plugin-transform-async-to-generator" "^7.4.0" "@babel/plugin-transform-block-scoped-functions" "^7.2.0" "@babel/plugin-transform-block-scoping" "^7.4.0" - "@babel/plugin-transform-classes" "^7.4.0" + "@babel/plugin-transform-classes" "^7.4.3" "@babel/plugin-transform-computed-properties" "^7.2.0" - "@babel/plugin-transform-destructuring" "^7.4.0" - "@babel/plugin-transform-dotall-regex" "^7.2.0" + "@babel/plugin-transform-destructuring" "^7.4.3" + "@babel/plugin-transform-dotall-regex" "^7.4.3" "@babel/plugin-transform-duplicate-keys" "^7.2.0" "@babel/plugin-transform-exponentiation-operator" "^7.2.0" - "@babel/plugin-transform-for-of" "^7.4.0" - "@babel/plugin-transform-function-name" "^7.2.0" + "@babel/plugin-transform-for-of" "^7.4.3" + "@babel/plugin-transform-function-name" "^7.4.3" "@babel/plugin-transform-literals" "^7.2.0" + "@babel/plugin-transform-member-expression-literals" "^7.2.0" "@babel/plugin-transform-modules-amd" "^7.2.0" - "@babel/plugin-transform-modules-commonjs" "^7.4.0" + "@babel/plugin-transform-modules-commonjs" "^7.4.3" "@babel/plugin-transform-modules-systemjs" "^7.4.0" "@babel/plugin-transform-modules-umd" "^7.2.0" "@babel/plugin-transform-named-capturing-groups-regex" "^7.4.2" "@babel/plugin-transform-new-target" "^7.4.0" "@babel/plugin-transform-object-super" "^7.2.0" - "@babel/plugin-transform-parameters" "^7.4.0" - "@babel/plugin-transform-regenerator" "^7.4.0" + "@babel/plugin-transform-parameters" "^7.4.3" + "@babel/plugin-transform-property-literals" "^7.2.0" + "@babel/plugin-transform-regenerator" "^7.4.3" + "@babel/plugin-transform-reserved-words" "^7.2.0" "@babel/plugin-transform-shorthand-properties" "^7.2.0" "@babel/plugin-transform-spread" "^7.2.0" "@babel/plugin-transform-sticky-regex" "^7.2.0" "@babel/plugin-transform-template-literals" "^7.2.0" "@babel/plugin-transform-typeof-symbol" "^7.2.0" - "@babel/plugin-transform-unicode-regex" "^7.2.0" + "@babel/plugin-transform-unicode-regex" "^7.4.3" "@babel/types" "^7.4.0" - browserslist "^4.4.2" + browserslist "^4.5.2" core-js-compat "^3.0.0" invariant "^2.2.2" js-levenshtein "^1.1.3" - semver "^5.3.0" + semver "^5.5.0" "@babel/preset-react@^7.0.0": version "7.0.0" @@ -741,13 +818,20 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-transform-typescript" "^7.3.2" -"@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4": +"@babel/runtime@^7.3.1": version "7.4.0" resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.0.tgz#d523416573f19aa12784639e631257c7fc58c0aa" integrity sha512-/eftZ45kD0OfOFHAmN02WP6N1NVphY+lBf8c2Q/P9VW3tj+N5NlBBAWfqOLOl96YDGMqpIBO5O/hQNx4A/lAng== dependencies: regenerator-runtime "^0.13.2" +"@babel/runtime@^7.4.2": + version "7.4.3" + resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.3.tgz#79888e452034223ad9609187a0ad1fe0d2ad4bdc" + integrity sha512-9lsJwJLxDh/T3Q3SZszfWOTkk3pHbkmH+3KY+zwIDmsNlxsumuhS2TH3NIpktU4kNvfzy+k3eLT7aTJSPTo0OA== + dependencies: + regenerator-runtime "^0.13.2" + "@babel/template@^7.0.0", "@babel/template@^7.1.0", "@babel/template@^7.2.2", "@babel/template@^7.4.0": version "7.4.0" resolved "https://registry.npmjs.org/@babel/template/-/template-7.4.0.tgz#12474e9c077bae585c5d835a95c0b0b790c25c8b" @@ -772,6 +856,21 @@ globals "^11.1.0" lodash "^4.17.11" +"@babel/traverse@^7.4.3": + version "7.4.3" + resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.4.3.tgz#1a01f078fc575d589ff30c0f71bf3c3d9ccbad84" + integrity sha512-HmA01qrtaCwwJWpSKpA948cBvU5BrmviAief/b3AVw936DtcdsTexlbyzNuDnthwhOQ37xshn7hvQaEQk7ISYQ== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/generator" "^7.4.0" + "@babel/helper-function-name" "^7.1.0" + "@babel/helper-split-export-declaration" "^7.4.0" + "@babel/parser" "^7.4.3" + "@babel/types" "^7.4.0" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.11" + "@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.2.2", "@babel/types@^7.3.0", "@babel/types@^7.4.0": version "7.4.0" resolved "https://registry.npmjs.org/@babel/types/-/types-7.4.0.tgz#670724f77d24cce6cc7d8cf64599d511d164894c" @@ -781,75 +880,75 @@ lodash "^4.17.11" to-fast-properties "^2.0.0" -"@beemo/cli@^1.0.0-alpha.2": - version "1.0.0-alpha.2" - resolved "https://registry.npmjs.org/@beemo/cli/-/cli-1.0.0-alpha.2.tgz#3dc55056ecb9409650314268dba18c62d408a43d" - integrity sha512-htT18Jk/2XagmuiuC+FJu7Ldca0UTCFHFmSMCI8h5D5+i7by2LratpDp4R1mlE8mWKsMP0MYr9pj8GEuIufhHg== +"@beemo/cli@^1.0.0-alpha.3": + version "1.0.0-alpha.3" + resolved "https://registry.npmjs.org/@beemo/cli/-/cli-1.0.0-alpha.3.tgz#c52d1be69d1c2e6c67824abca6b86c74b6b680f1" + integrity sha512-MpHspCM2lgqGbY7c+kvpdGi4EyqBptJyDHJo69wD1wdrIU4ZTuvXChEwqGJJ3cqPxLeq3g4JuuR2qKvSDzW2mw== dependencies: chalk "^2.4.2" - semver "^5.6.0" + semver "^6.0.0" yargs "^13.2.2" -"@beemo/core@^1.0.0-alpha.1": - version "1.0.0-alpha.1" - resolved "https://registry.npmjs.org/@beemo/core/-/core-1.0.0-alpha.1.tgz#631a5e1c1ff5db46a22ce9bd6ff7a44868080a52" - integrity sha512-JkE4t66ux1JiZBV0+BFVaXHoVKRdU8oDsM8T2VW+5qkJYE2poT2jWkiqmPGsGTlpMLtyuKrG86xRpPZ3yTNGDQ== +"@beemo/core@^1.0.0-alpha.3": + version "1.0.0-alpha.3" + resolved "https://registry.npmjs.org/@beemo/core/-/core-1.0.0-alpha.3.tgz#74b6300057e83ba99152f34e5a960ba62eaeca91" + integrity sha512-70vpqKfjesHqY2rwadFI+88KL+GQZvOMO4BaMh+QsqQCDC2a4h49ZUXADSKI3ojogO9DmKgVE6aRmTjxTlEIqg== dependencies: - "@beemo/dependency-graph" "^1.0.0-alpha.0" - "@boost/core" "^1.9.3" + "@beemo/dependency-graph" "^1.0.0-alpha.1" + "@boost/core" "^1.10.1" "@types/execa" "^0.9.0" "@types/fs-extra" "^5.0.5" - "@types/is-glob" "^4.0.0" + "@types/is-glob" "^4.0.1" "@types/lodash" "^4.14.123" "@types/micromatch" "^3.1.0" - "@types/node" "^11.11.1" - "@types/yargs" "^12.0.9" + "@types/node" "^11.12.1" + "@types/yargs" "^12.0.10" "@types/yargs-parser" "^11.0.2" chalk "^2.4.2" execa "^1.0.0" fast-glob "^2.2.6" fs-extra "^7.0.1" hygen "^4.0.2" - is-glob "^4.0.0" + is-glob "^4.0.1" lodash "^4.17.11" micromatch "^3.1.10" optimal "^2.1.1" yargs "^13.2.2" yargs-parser "^13.0.0" -"@beemo/dependency-graph@^1.0.0-alpha.0": - version "1.0.0-alpha.0" - resolved "https://registry.npmjs.org/@beemo/dependency-graph/-/dependency-graph-1.0.0-alpha.0.tgz#416b7be9755fd02545ccbee703fa6d3d6e60fd7d" - integrity sha512-FnLYxfzV2+0U4bGazXICTa/fm+feEsUjofEUfrkwKDhxKoGeV5azBd1DgBM0H01PFNiTw9ISTuYcykfH8pJJQQ== - -"@beemo/driver-babel@^1.0.0-alpha.1": +"@beemo/dependency-graph@^1.0.0-alpha.1": version "1.0.0-alpha.1" - resolved "https://registry.npmjs.org/@beemo/driver-babel/-/driver-babel-1.0.0-alpha.1.tgz#f476dae70523f9117c3277d9a3f2f7a41774bd47" - integrity sha512-T4Xweq+ADNGByHQctBVIeAQ0P1rRbRhXlNNEtTQ40dj/JEyBijcScvs46aeZX2yvl64jTg0kkIjDVAMtopNTTw== + resolved "https://registry.npmjs.org/@beemo/dependency-graph/-/dependency-graph-1.0.0-alpha.1.tgz#282b7c01f8d6a8f7c599ccd4c65342daa6e09f38" + integrity sha512-WRVXnT1EUgsUnv8A630qsnZLMmHv+JqztvwPuGEDDykeZcEow5VNsj3IAopTd7EDhdmj+PRyqho9wHtF67nfQQ== + +"@beemo/driver-babel@^1.0.0-alpha.2": + version "1.0.0-alpha.2" + resolved "https://registry.npmjs.org/@beemo/driver-babel/-/driver-babel-1.0.0-alpha.2.tgz#7a9e31ef148d7107c56a360a5fd81dbe1d7c47ff" + integrity sha512-5pwGpQS7Ih+czrv5SpbEkqmIuLk8V+Yd7GU7B/8FxWVhJ5nFzo9AOz05ea+S1HYdegHND5yJfGN4o7N378DViQ== dependencies: rimraf "^2.6.3" -"@beemo/driver-eslint@^1.0.0-alpha.1": - version "1.0.0-alpha.1" - resolved "https://registry.npmjs.org/@beemo/driver-eslint/-/driver-eslint-1.0.0-alpha.1.tgz#408d734334ef2a3e0960c0afff832c207bfc1fb1" - integrity sha512-S9MQoFS86k3zF10WznxAqA3YucGju9+8Vf1BY2ulE83Kj2n7Ix8zaCO4GXIjBLGewXjBqyOj31UhcMqyKjAT6w== +"@beemo/driver-eslint@^1.0.0-alpha.3": + version "1.0.0-alpha.3" + resolved "https://registry.npmjs.org/@beemo/driver-eslint/-/driver-eslint-1.0.0-alpha.3.tgz#6cf86cc048463972ffbbd47fe2ea76a7be8f9823" + integrity sha512-wCDYyE3TmCrP0twgnWWr+Nmy+AiIWYVTf36Fhpys4om81x0CifRKsd7U3cghk/jplshXH5+6jBnGj/jPMvI7jA== -"@beemo/driver-jest@^1.0.0-alpha.1": - version "1.0.0-alpha.1" - resolved "https://registry.npmjs.org/@beemo/driver-jest/-/driver-jest-1.0.0-alpha.1.tgz#8268369576dd54bdb09663f2c8c3c425cd1de20c" - integrity sha512-4g3dwi+RmKNLJ6fAHyfellrDEQAXcGXGgsQ+uN9kRvzflauK0XymJyLjN0o9ZO1QUUl5kBizbnuUQQ7mCau9UA== +"@beemo/driver-jest@^1.0.0-alpha.2": + version "1.0.0-alpha.2" + resolved "https://registry.npmjs.org/@beemo/driver-jest/-/driver-jest-1.0.0-alpha.2.tgz#d5bd18d9807485548586bb1c8c1562b60a0a9abb" + integrity sha512-ZFrnTSLaERGbrY0z4ISIlO8I0IFuJ9ah2vwryR8br06taJuAMwGBbEtxOU23wHr1brgQ2o+3scFirSKrfU6vYA== -"@beemo/driver-prettier@^1.0.0-alpha.1": - version "1.0.0-alpha.1" - resolved "https://registry.npmjs.org/@beemo/driver-prettier/-/driver-prettier-1.0.0-alpha.1.tgz#1b1b5d33c426fe95dc2259562453cb210dfde487" - integrity sha512-on2v3/Bm6LCrBN2AYlraFWPtTVfnbWpP6Ntti8WaconsGirlJ7aZa3CATT1n1GfvRRxcsf+djgOCMDBNTtEVdQ== +"@beemo/driver-prettier@^1.0.0-alpha.3": + version "1.0.0-alpha.3" + resolved "https://registry.npmjs.org/@beemo/driver-prettier/-/driver-prettier-1.0.0-alpha.3.tgz#39d2645acf8accceb3ebd7f73faf13890282237a" + integrity sha512-hfw8pXiddp9dsuR2Z91/tbM7fJGW/2l7nUvPX3bTdTZa/h/xtLd9UPtYT4MmTpZRKWTFTMCJ2P8zH5lDhjxs3w== dependencies: "@types/prettier" "^1.16.1" -"@beemo/driver-typescript@^1.0.0-alpha.1": - version "1.0.0-alpha.1" - resolved "https://registry.npmjs.org/@beemo/driver-typescript/-/driver-typescript-1.0.0-alpha.1.tgz#ec93329574f3bf1c75b78b88206dd280f0be5738" - integrity sha512-HDKQZtkRmHeqEivDsuOgsj4KhlckFcLA3QAMmXES2ZTK4ddRPLSljuDnQp/gFVSH0Ze150Xnyq6UgXMrrFpfdg== +"@beemo/driver-typescript@^1.0.0-alpha.3": + version "1.0.0-alpha.3" + resolved "https://registry.npmjs.org/@beemo/driver-typescript/-/driver-typescript-1.0.0-alpha.3.tgz#18f7557db07de4669b3f78ead8ed187f44b8fa49" + integrity sha512-mJxXIIM5HmQ4gdwVeThvofeTWwZqKrBxhRv1DXYgTZm9DvMfGycd2hVhE6Q1kGN2Er6LzXDpG8/bnYPiep+e/Q== dependencies: rimraf "^2.6.3" @@ -1618,79 +1717,79 @@ npmlog "^4.1.2" write-file-atomic "^2.3.0" -"@milesj/build-tool-config@^0.156.0": - version "0.156.0" - resolved "https://registry.npmjs.org/@milesj/build-tool-config/-/build-tool-config-0.156.0.tgz#49df925a88b93fff7f9eb45e116973eec2a9f27e" - integrity sha512-K7mdMPmz9ZHaGvioTuRZXR3AdGj4am8copZNkcUFLVvf8naTZX5HLCWnI+oQP5rD0ThQksjIwTgTPIkbb25mjg== +"@milesj/build-tool-config@^0.159.2": + version "0.159.2" + resolved "https://registry.npmjs.org/@milesj/build-tool-config/-/build-tool-config-0.159.2.tgz#b06edc3fbbd9cd04a22a4f43de1a203e00de3aa7" + integrity sha512-xor3EiAj+LTD9pk7HX/ChggviwE7UVU+Zmsd0uLYANbgi/8MiROB9cSqGtICufxYa9icBdo8T2VtgwkPYzjjbw== dependencies: - "@babel/cli" "^7.2.3" - "@babel/core" "^7.4.0" + "@babel/cli" "^7.4.3" + "@babel/core" "^7.4.3" "@babel/plugin-proposal-class-properties" "^7.4.0" "@babel/plugin-proposal-export-default-from" "^7.2.0" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.2.0" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.4.3" "@babel/plugin-proposal-optional-catch-binding" "^7.2.0" "@babel/plugin-proposal-optional-chaining" "^7.2.0" "@babel/plugin-syntax-dynamic-import" "^7.2.0" - "@babel/plugin-transform-runtime" "^7.4.0" - "@babel/preset-env" "^7.4.2" + "@babel/plugin-transform-runtime" "^7.4.3" + "@babel/preset-env" "^7.4.3" "@babel/preset-react" "^7.0.0" "@babel/preset-typescript" "^7.3.3" "@types/enzyme" "^3.9.1" "@types/jest" "^24.0.11" - "@typescript-eslint/eslint-plugin" "~1.5.0" - "@typescript-eslint/parser" "~1.5.0" + "@typescript-eslint/eslint-plugin" "~1.6.0" + "@typescript-eslint/parser" "~1.6.0" babel-plugin-transform-dev "^2.0.1" babel-plugin-typescript-to-proptypes "^0.17.1" enzyme "^3.9.0" enzyme-adapter-react-16 "^1.11.2" enzyme-to-json "^3.3.5" - eslint "^5.15.3" + eslint "^5.16.0" eslint-config-airbnb "^17.1.0" eslint-config-prettier "^4.1.0" eslint-plugin-babel "^5.3.0" - eslint-plugin-compat "^3.0.1" + eslint-plugin-compat "^3.1.0" eslint-plugin-import "^2.16.0" eslint-plugin-jest "^22.4.1" eslint-plugin-jsx-a11y "^6.2.1" - eslint-plugin-promise "^4.0.1" + eslint-plugin-promise "^4.1.1" eslint-plugin-react "^7.12.4" eslint-plugin-react-hooks "^1.6.0" - eslint-plugin-unicorn "^7.1.0" + eslint-plugin-unicorn "^8.0.1" execa "^1.0.0" fs-extra "^7.0.1" jest "^24.5.0" np "^4.0.2" prettier "^1.16.4" - react "^16.8.5" - react-dom "^16.8.5" - react-test-renderer "^16.8.5" - typescript "~3.3.4000" - -"@milesj/build-tool-runtime@^0.30.0": - version "0.30.0" - resolved "https://registry.npmjs.org/@milesj/build-tool-runtime/-/build-tool-runtime-0.30.0.tgz#7fce2315d8797d7952fee0980998d4f708bdb789" - integrity sha512-qodGCOW//mS1BtDB9wQ10hWvWXs8oc2a+pJWReZFTW13mGTgdVFnx84HOucfXxXTnIfJYK6KWmnr5auDhbBcCA== - dependencies: - "@babel/core" "^7.4.0" - "@beemo/cli" "^1.0.0-alpha.2" - "@beemo/core" "^1.0.0-alpha.1" - "@beemo/driver-babel" "^1.0.0-alpha.1" - "@beemo/driver-eslint" "^1.0.0-alpha.1" - "@beemo/driver-jest" "^1.0.0-alpha.1" - "@beemo/driver-prettier" "^1.0.0-alpha.1" - "@beemo/driver-typescript" "^1.0.0-alpha.1" - eslint "^5.15.3" + react "^16.8.6" + react-dom "^16.8.6" + react-test-renderer "^16.8.6" + typescript "~3.4.1" + +"@milesj/build-tool-runtime@^0.33.0": + version "0.33.0" + resolved "https://registry.npmjs.org/@milesj/build-tool-runtime/-/build-tool-runtime-0.33.0.tgz#fdfcd1f0adb3250b899394bd7b5c9b8381d1698b" + integrity sha512-vH/R+jAvTcvFC/GwqClLwFrBpYSW+O84reUh2XllxnLc+kTWxuLwgEKlqRNKNJL4dTzge1vt+p33k+LIujMXlw== + dependencies: + "@babel/core" "^7.4.3" + "@beemo/cli" "^1.0.0-alpha.3" + "@beemo/core" "^1.0.0-alpha.3" + "@beemo/driver-babel" "^1.0.0-alpha.2" + "@beemo/driver-eslint" "^1.0.0-alpha.3" + "@beemo/driver-jest" "^1.0.0-alpha.2" + "@beemo/driver-prettier" "^1.0.0-alpha.3" + "@beemo/driver-typescript" "^1.0.0-alpha.3" + eslint "^5.16.0" jest "^24.5.0" prettier "^1.16.4" - typescript "^3.3.4000" + typescript "^3.4.1" -"@milesj/build-tools@^0.34.0": - version "0.34.0" - resolved "https://registry.npmjs.org/@milesj/build-tools/-/build-tools-0.34.0.tgz#790f970028f73bea5656f73ffe32647f51c618c1" - integrity sha512-StyaJSzYuYG0+jinLS9WLqT7LS2/RQIMX7x5LIVk+9sFpg3fiLCFzwTC3XFmN8zbQdAbil0SstWvLuKaf2Xngg== +"@milesj/build-tools@^0.37.2": + version "0.37.2" + resolved "https://registry.npmjs.org/@milesj/build-tools/-/build-tools-0.37.2.tgz#9eff87b1e249835775b26ca1f79b5d17ae6ad110" + integrity sha512-GdvR0BbM/alH4hzrIAfrwaScrbsKBLLv/E13iVdTaTO5kUs0FsooRnvnG9j23uD/0a5u6O4sEWEXghmNhsiwxw== dependencies: - "@milesj/build-tool-config" "^0.156.0" - "@milesj/build-tool-runtime" "^0.30.0" + "@milesj/build-tool-config" "^0.159.2" + "@milesj/build-tool-runtime" "^0.33.0" "@mrmlnc/readdir-enhanced@^2.2.1": version "2.2.1" @@ -1850,10 +1949,10 @@ dependencies: "@types/node" "*" -"@types/is-glob@^4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@types/is-glob/-/is-glob-4.0.0.tgz#fb8a2bff539025d4dcd6d5efe7689e03341b876d" - integrity sha512-zC/2EmD8scdsGIeE+Xg7kP7oi9VP90zgMQtm9Cr25av4V+a+k8slQyiT60qSw8KORYrOKlPXfHwoa1bQbRzskQ== +"@types/is-glob@^4.0.1": + version "4.0.1" + resolved "https://registry.npmjs.org/@types/is-glob/-/is-glob-4.0.1.tgz#a93eec1714172c8eb3225a1cc5eb88c2477b7d00" + integrity sha512-k3RS5HyBPu4h+5hTmIEfPB2rl5P3LnGdQEZrV2b9OWTJVtsUQ2VBcedqYKGqxvZqle5UALUXdSfVA8nf3HfyWQ== "@types/istanbul-lib-coverage@^1.1.0": version "1.1.0" @@ -1889,11 +1988,16 @@ dependencies: "@types/braces" "*" -"@types/node@*", "@types/node@^11.11.1": +"@types/node@*": version "11.11.4" resolved "https://registry.npmjs.org/@types/node/-/node-11.11.4.tgz#8808bd5a82bbf6f5d412eff1c228d178e7c24bb3" integrity sha512-02tIL+QIi/RW4E5xILdoAMjeJ9kYq5t5S2vciUdFPXv/ikFTb0zK8q9vXkg4+WAJuYXGiVT1H28AkD2C+IkXVw== +"@types/node@^11.12.1": + version "11.13.0" + resolved "https://registry.npmjs.org/@types/node/-/node-11.13.0.tgz#b0df8d6ef9b5001b2be3a94d909ce3c29a80f9e1" + integrity sha512-rx29MMkRdVmzunmiA4lzBYJNnXsW/PhG4kMBy2ATsYaDjGGR75dCFEVVROKpNwlVdcUX3xxlghKQOeDPBJobng== + "@types/os-locale@^2.1.0": version "2.1.0" resolved "https://registry.npmjs.org/@types/os-locale/-/os-locale-2.1.0.tgz#0ded736612a79e900fa76f02c6ad566d046ad17a" @@ -1967,29 +2071,29 @@ resolved "https://registry.npmjs.org/@types/yargs/-/yargs-12.0.10.tgz#17a8ec65cd8e88f51b418ceb271af18d3137df67" integrity sha512-WsVzTPshvCSbHThUduGGxbmnwcpkgSctHGHTqzWyFg4lYAuV5qXlyFPOsP3OWqCINfmg/8VXP+zJaa4OxEsBQQ== -"@typescript-eslint/eslint-plugin@~1.5.0": - version "1.5.0" - resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-1.5.0.tgz#85c509bcfc2eb35f37958fa677379c80b7a8f66f" - integrity sha512-TZ5HRDFz6CswqBUviPX8EfS+iOoGbclYroZKT3GWGYiGScX0qo6QjHc5uuM7JN920voP2zgCkHgF5SDEVlCtjQ== +"@typescript-eslint/eslint-plugin@~1.6.0": + version "1.6.0" + resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-1.6.0.tgz#a5ff3128c692393fb16efa403ec7c8a5593dab0f" + integrity sha512-U224c29E2lo861TQZs6GSmyC0OYeRNg6bE9UVIiFBxN2MlA0nq2dCrgIVyyRbC05UOcrgf2Wk/CF2gGOPQKUSQ== dependencies: - "@typescript-eslint/parser" "1.5.0" - "@typescript-eslint/typescript-estree" "1.5.0" + "@typescript-eslint/parser" "1.6.0" + "@typescript-eslint/typescript-estree" "1.6.0" requireindex "^1.2.0" tsutils "^3.7.0" -"@typescript-eslint/parser@1.5.0", "@typescript-eslint/parser@~1.5.0": - version "1.5.0" - resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-1.5.0.tgz#a96114d195dff2a49534e4c4850fb676f905a072" - integrity sha512-pRWTnJrnxuT0ragdY26hZL+bxqDd4liMlftpH2CBlMPryOIOb1J+MdZuw6R4tIu6bWVdwbHKPTs+Q34LuGvfGw== +"@typescript-eslint/parser@1.6.0", "@typescript-eslint/parser@~1.6.0": + version "1.6.0" + resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-1.6.0.tgz#f01189c8b90848e3b8e45a6cdad27870529d1804" + integrity sha512-VB9xmSbfafI+/kI4gUK3PfrkGmrJQfh0N4EScT1gZXSZyUxpsBirPL99EWZg9MmPG0pzq/gMtgkk7/rAHj4aQw== dependencies: - "@typescript-eslint/typescript-estree" "1.5.0" + "@typescript-eslint/typescript-estree" "1.6.0" eslint-scope "^4.0.0" eslint-visitor-keys "^1.0.0" -"@typescript-eslint/typescript-estree@1.5.0": - version "1.5.0" - resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-1.5.0.tgz#986b356ecdf5a0c3bc9889d221802149cf5dbd4e" - integrity sha512-XqR14d4BcYgxcrpxIwcee7UEjncl9emKc/MgkeUfIk2u85KlsGYyaxC7Zxjmb17JtWERk/NaO+KnBsqgpIXzwA== +"@typescript-eslint/typescript-estree@1.6.0": + version "1.6.0" + resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-1.6.0.tgz#6cf43a07fee08b8eb52e4513b428c8cdc9751ef0" + integrity sha512-A4CanUwfaG4oXobD5y7EXbsOHjCwn8tj1RDd820etpPAjH+Icjc2K9e/DQM1Hac5zH2BSy+u6bjvvF2wwREvYA== dependencies: lodash.unescape "4.0.1" semver "5.5.0" @@ -2480,7 +2584,7 @@ browser-resolve@^1.11.3: dependencies: resolve "1.1.7" -browserslist@^4.4.2, browserslist@^4.5.1: +browserslist@^4.5.1: version "4.5.1" resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.5.1.tgz#2226cada1947b33f4cfcf7b608dcb519b6128106" integrity sha512-/pPw5IAUyqaQXGuD5vS8tcbudyPZ241jk1W5pQBsGDfcjNQt7p8qxZhgMNuygDShte1PibLFexecWUPgmVLfrg== @@ -2489,6 +2593,15 @@ browserslist@^4.4.2, browserslist@^4.5.1: electron-to-chromium "^1.3.116" node-releases "^1.1.11" +browserslist@^4.5.2: + version "4.5.4" + resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.5.4.tgz#166c4ecef3b51737a42436ea8002aeea466ea2c7" + integrity sha512-rAjx494LMjqKnMPhFkuLmLp8JWEX0o8ADTGeAbOqaF+XCvYLreZrG5uVjnPBlAQ8REZK4pzXGvp0bWgrFtKaag== + dependencies: + caniuse-lite "^1.0.30000955" + electron-to-chromium "^1.3.122" + node-releases "^1.1.13" + bser@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/bser/-/bser-2.0.0.tgz#9ac78d3ed5d915804fd87acb158bc797147a1719" @@ -2638,16 +2751,21 @@ camelcase@^5.0.0: resolved "https://registry.npmjs.org/camelcase/-/camelcase-5.2.0.tgz#e7522abda5ed94cc0489e1b8466610e88404cf45" integrity sha512-IXFsBS2pC+X0j0N/GE7Dm7j3bsEBp+oTpb7F50dwEVX7rf3IgwO9XatnegTsDtniKCUtEJH4fSU6Asw7uoVLfQ== -caniuse-db@^1.0.30000947: - version "1.0.30000950" - resolved "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000950.tgz#d8356a082b28bc22975f010fc7668fcb77e1339f" - integrity sha512-mS/KbErOeYOVF8W4GdMmHAyNm9p4XGXLP4Nde75b1uadwzUr+dFd0RCVheQIB8+yKs1PGW73Bnejch0G1s0FvQ== +caniuse-db@^1.0.30000951: + version "1.0.30000956" + resolved "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000956.tgz#af84fc7629fd32b515294cd33f4cb2c06645f866" + integrity sha512-qA9aAldTqeW+5HOQVIoTpYlHm4xLek9jA1Ch29ego7sZfI3gQdzZ/e4nV4eeYab2Qong6uTrydOBLaPhvYnzJA== caniuse-lite@^1.0.30000949: version "1.0.30000950" resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000950.tgz#8c559d66e332b34e919d1086cc6d29c1948856ae" integrity sha512-Cs+4U9T0okW2ftBsCIHuEYXXkki7mjXmjCh4c6PzYShk04qDEr76/iC7KwhLoWoY65wcra1XOsRD+S7BptEb5A== +caniuse-lite@^1.0.30000955: + version "1.0.30000955" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000955.tgz#360fdb9a1e41d6dd996130411334e44a39e4446d" + integrity sha512-6AwmIKgqCYfDWWadRkAuZSHMQP4Mmy96xAXEdRBlN/luQhlRYOKgwOlZ9plpCOsVbBuqbTmGqDK3JUM/nlr8CA== + capture-exit@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4" @@ -2731,10 +2849,10 @@ cheerio@^1.0.0-rc.2: lodash "^4.15.0" parse5 "^3.0.1" -chokidar@^2.0.3: - version "2.1.2" - resolved "https://registry.npmjs.org/chokidar/-/chokidar-2.1.2.tgz#9c23ea40b01638439e0513864d362aeacc5ad058" - integrity sha512-IwXUx0FXc5ibYmPC2XeEj5mpXoV66sR+t3jqu2NS2GYwCktt3KF1/Qqjws/NkegajBA4RbZ5+DDwlOiJsxDHEg== +chokidar@^2.0.4: + version "2.1.5" + resolved "https://registry.npmjs.org/chokidar/-/chokidar-2.1.5.tgz#0ae8434d962281a5f56c72869e79cb6d9d86ad4d" + integrity sha512-i0TprVWp+Kj4WRPtInjexJ8Q+BqTE909VpH8xVhXrJkoc5QC8VO9TryGOqTr+2hljzc1sC62t22h5tZePodM/A== dependencies: anymatch "^2.0.0" async-each "^1.0.1" @@ -2746,7 +2864,7 @@ chokidar@^2.0.3: normalize-path "^3.0.0" path-is-absolute "^1.0.0" readdirp "^2.2.1" - upath "^1.1.0" + upath "^1.1.1" optionalDependencies: fsevents "^1.2.7" @@ -3540,6 +3658,11 @@ electron-to-chromium@^1.3.116: resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.116.tgz#1dbfee6a592a0c14ade77dbdfe54fef86387d702" integrity sha512-NKwKAXzur5vFCZYBHpdWjTMO8QptNLNP80nItkSIgUOapPAo9Uia+RvkCaZJtO7fhQaVElSvBPWEc2ku6cKsPA== +electron-to-chromium@^1.3.122: + version "1.3.122" + resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.122.tgz#b32a0805f48557bd3c3b8104eadc7fa511b14a9a" + integrity sha512-3RKoIyCN4DhP2dsmleuFvpJAIDOseWH88wFYBzb22CSwoFDSWRc4UAMfrtc9h8nBdJjTNIN3rogChgOy6eFInw== + elegant-spinner@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e" @@ -3760,16 +3883,16 @@ eslint-plugin-babel@^5.3.0: dependencies: eslint-rule-composer "^0.3.0" -eslint-plugin-compat@^3.0.1: - version "3.0.1" - resolved "https://registry.npmjs.org/eslint-plugin-compat/-/eslint-plugin-compat-3.0.1.tgz#46b5b88a27257a153255b91b41f783da7910e864" - integrity sha512-i0hx+Fm2A2rn35xtCpRAdg1tawe+Gsv8ydR3s6UMINGy9HzW1Yw7ojnm9E/bSznWjdIatKN/cVAWbG/uy4Munw== +eslint-plugin-compat@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/eslint-plugin-compat/-/eslint-plugin-compat-3.1.0.tgz#3a0a0490bd8cb08eb06cbbe840ad612227ccadf2" + integrity sha512-N+FiugxY5Y8CKp6/sk76KtIFlA2fMx8Ogb3FqbwOePND6mnnkxZL/iPMC/3QhPtuq10RVNduYOmKU9laqna5+A== dependencies: - "@babel/runtime" "^7.3.4" + "@babel/runtime" "^7.4.2" ast-metadata-inferer "^0.1.1" - browserslist "^4.4.2" - caniuse-db "^1.0.30000947" - mdn-browser-compat-data "^0.0.70" + browserslist "^4.5.2" + caniuse-db "^1.0.30000951" + mdn-browser-compat-data "^0.0.72" semver "^5.6.0" eslint-plugin-import@^2.16.0: @@ -3807,10 +3930,10 @@ eslint-plugin-jsx-a11y@^6.2.1: has "^1.0.3" jsx-ast-utils "^2.0.1" -eslint-plugin-promise@^4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.0.1.tgz#2d074b653f35a23d1ba89d8e976a985117d1c6a2" - integrity sha512-Si16O0+Hqz1gDHsys6RtFRrW7cCTB6P7p3OJmKp3Y3dxpQE2qwOA7d3xnV+0mBmrPoi0RBnxlCKvqu70te6wjg== +eslint-plugin-promise@^4.1.1: + version "4.1.1" + resolved "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.1.1.tgz#1e08cb68b5b2cd8839f8d5864c796f56d82746db" + integrity sha512-faAHw7uzlNPy7b45J1guyjazw28M+7gJokKUjC5JSFoYfUEyy6Gw/i7YQvmv2Yk00sUjWcmzXQLpU1Ki/C2IZQ== eslint-plugin-react-hooks@^1.6.0: version "1.6.0" @@ -3830,18 +3953,21 @@ eslint-plugin-react@^7.12.4: prop-types "^15.6.2" resolve "^1.9.0" -eslint-plugin-unicorn@^7.1.0: - version "7.1.0" - resolved "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-7.1.0.tgz#9efef5c68fde0ebefb0241fbcfa274f1b959c04e" - integrity sha512-lW/ZwGR638V0XuZgR160qVQvPtw8tw3laKT5LjJPt+W+tN7kVf2S2V7x+ZrEEwSjEb3OiEzb3cppzaKuYtgYeg== +eslint-plugin-unicorn@^8.0.1: + version "8.0.1" + resolved "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-8.0.1.tgz#9212c4d8dd729785db846db920148ca97d545696" + integrity sha512-BIkPbCww71GZPnT1U4jzlNQyabmV7JlHu3qhCGLkZZKFp7jWkWdCQziadYdpLFLD5A+Z2nSi1LPe8/fkesvPVg== dependencies: clean-regexp "^1.0.0" eslint-ast-utils "^1.0.0" import-modules "^1.1.0" lodash.camelcase "^4.1.1" + lodash.defaultsdeep "^4.6.0" lodash.kebabcase "^4.0.1" lodash.snakecase "^4.0.1" + lodash.topairs "^4.3.0" lodash.upperfirst "^4.2.0" + reserved-words "^0.1.2" safe-regex "^2.0.1" eslint-restricted-globals@^0.1.1: @@ -3872,10 +3998,10 @@ eslint-visitor-keys@^1.0.0: resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d" integrity sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ== -eslint@^5.15.3: - version "5.15.3" - resolved "https://registry.npmjs.org/eslint/-/eslint-5.15.3.tgz#c79c3909dc8a7fa3714fb340c11e30fd2526b8b5" - integrity sha512-vMGi0PjCHSokZxE0NLp2VneGw5sio7SSiDNgIUn2tC0XkWJRNOIoHIg3CliLVfXnJsiHxGAYrkw0PieAu8+KYQ== +eslint@^5.16.0: + version "5.16.0" + resolved "https://registry.npmjs.org/eslint/-/eslint-5.16.0.tgz#a1e3ac1aae4a3fbd8296fcf8f7ab7314cbb6abea" + integrity sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg== dependencies: "@babel/code-frame" "^7.0.0" ajv "^6.9.1" @@ -3897,7 +4023,7 @@ eslint@^5.15.3: import-fresh "^3.0.0" imurmurhash "^0.1.4" inquirer "^6.2.2" - js-yaml "^3.12.0" + js-yaml "^3.13.0" json-stable-stringify-without-jsonify "^1.0.1" levn "^0.3.0" lodash "^4.17.11" @@ -5099,6 +5225,13 @@ is-glob@^4.0.0: dependencies: is-extglob "^2.1.1" +is-glob@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + dependencies: + is-extglob "^2.1.1" + is-installed-globally@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80" @@ -5742,6 +5875,14 @@ js-yaml@^3.10.0, js-yaml@^3.12.0, js-yaml@^3.9.0: argparse "^1.0.7" esprima "^4.0.0" +js-yaml@^3.13.0: + version "3.13.0" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.0.tgz#38ee7178ac0eea2c97ff6d96fff4b18c7d8cf98e" + integrity sha512-pZZoSxcCYco+DIKBTimr67J6Hy+EYGZDY/HCWC+iAEA9h1ByhMXAIVUXMcMFpOCxQ/xjXmPI2MkDL5HRm5eFrQ== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + jsbn@~0.1.0: version "0.1.1" resolved "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" @@ -6086,6 +6227,11 @@ lodash.clonedeep@^4.5.0: resolved "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= +lodash.defaultsdeep@^4.6.0: + version "4.6.0" + resolved "https://registry.npmjs.org/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.0.tgz#bec1024f85b1bd96cbea405b23c14ad6443a6f81" + integrity sha1-vsECT4WxvZbL6kBbI8FK1kQ6b4E= + lodash.escape@^4.0.1: version "4.0.1" resolved "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98" @@ -6141,6 +6287,11 @@ lodash.templatesettings@^4.0.0: dependencies: lodash._reinterpolate "~3.0.0" +lodash.topairs@^4.3.0: + version "4.3.0" + resolved "https://registry.npmjs.org/lodash.topairs/-/lodash.topairs-4.3.0.tgz#3b6deaa37d60fb116713c46c5f17ea190ec48d64" + integrity sha1-O23qo31g+xFnE8RsXxfqGQ7EjWQ= + lodash.unescape@4.0.1: version "4.0.1" resolved "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz#bf2249886ce514cda112fae9218cdc065211fc9c" @@ -6301,10 +6452,10 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" -mdn-browser-compat-data@^0.0.70: - version "0.0.70" - resolved "https://registry.npmjs.org/mdn-browser-compat-data/-/mdn-browser-compat-data-0.0.70.tgz#7eb8a7314f994538b70390e7048df1770fed9855" - integrity sha512-++gDpMmrHmZVIFTK9x3z7UjSAM3MYEwZe38yzNejVQe6OUHiLgRvlMW6TQznpIUGuWLesLGUGOg6iyj6JcTB2g== +mdn-browser-compat-data@^0.0.72: + version "0.0.72" + resolved "https://registry.npmjs.org/mdn-browser-compat-data/-/mdn-browser-compat-data-0.0.72.tgz#b1d2026bfbf61c82e71c4157059b9d161c8791a9" + integrity sha512-vt3BxJRpV638ncYLigX91k0qP1VcpKxgExqPtX+QKFvV4/ZruZ31Sl35LsDDq5q+D7Lt7mfGWnCEuZ0d6bJW1g== dependencies: extend "3.0.2" @@ -6689,6 +6840,13 @@ node-releases@^1.1.11: dependencies: semver "^5.3.0" +node-releases@^1.1.13: + version "1.1.13" + resolved "https://registry.npmjs.org/node-releases/-/node-releases-1.1.13.tgz#8c03296b5ae60c08e2ff4f8f22ae45bd2f210083" + integrity sha512-fKZGviSXR6YvVPyc011NHuJDSD8gFQvLPmc2d2V3BS4gr52ycyQ1Xzs7a8B+Ax3Ni/W+5h1h4SqmzeoA8WZRmA== + dependencies: + semver "^5.3.0" + "nopt@2 || 3": version "3.0.6" resolved "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" @@ -7641,22 +7799,27 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" -react-dom@^16.8.5: - version "16.8.5" - resolved "https://registry.npmjs.org/react-dom/-/react-dom-16.8.5.tgz#b3e37d152b49e07faaa8de41fdf562be3463335e" - integrity sha512-VIEIvZLpFafsfu4kgmftP5L8j7P1f0YThfVTrANMhZUFMDOsA6e0kfR6wxw/8xxKs4NB59TZYbxNdPCDW34x4w== +react-dom@^16.8.6: + version "16.8.6" + resolved "https://registry.npmjs.org/react-dom/-/react-dom-16.8.6.tgz#71d6303f631e8b0097f56165ef608f051ff6e10f" + integrity sha512-1nL7PIq9LTL3fthPqwkvr2zY7phIPjYrT0jp4HjyEQrEROnw4dG41VVwi/wfoCneoleqrNX7iAD+pXebJZwrwA== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" prop-types "^15.6.2" - scheduler "^0.13.5" + scheduler "^0.13.6" react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.5: version "16.8.5" resolved "https://registry.npmjs.org/react-is/-/react-is-16.8.5.tgz#c54ac229dd66b5afe0de5acbe47647c3da692ff8" integrity sha512-sudt2uq5P/2TznPV4Wtdi+Lnq3yaYW8LfvPKLM9BKD8jJNBkxMVyB0C9/GmVhLw7Jbdmndk/73n7XQGeN9A3QQ== -react-test-renderer@^16.0.0-0, react-test-renderer@^16.8.5: +react-is@^16.8.6: + version "16.8.6" + resolved "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16" + integrity sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA== + +react-test-renderer@^16.0.0-0: version "16.8.5" resolved "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.8.5.tgz#4cba7a8aad73f7e8a0bc4379a0fe21632886a563" integrity sha512-/pFpHYQH4f35OqOae/DgOCXJDxBqD3K3akVfDhLgR0qYHoHjnICI/XS9QDwIhbrOFHWL7okVW9kKMaHuKvt2ng== @@ -7666,15 +7829,25 @@ react-test-renderer@^16.0.0-0, react-test-renderer@^16.8.5: react-is "^16.8.5" scheduler "^0.13.5" -react@^16.8.5: - version "16.8.5" - resolved "https://registry.npmjs.org/react/-/react-16.8.5.tgz#49be3b655489d74504ad994016407e8a0445de66" - integrity sha512-daCb9TD6FZGvJ3sg8da1tRAtIuw29PbKZW++NN4wqkbEvxL+bZpaaYb4xuftW/SpXmgacf1skXl/ddX6CdOlDw== +react-test-renderer@^16.8.6: + version "16.8.6" + resolved "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.8.6.tgz#188d8029b8c39c786f998aa3efd3ffe7642d5ba1" + integrity sha512-H2srzU5IWYT6cZXof6AhUcx/wEyJddQ8l7cLM/F7gDXYyPr4oq+vCIxJYXVGhId1J706sqziAjuOEjyNkfgoEw== + dependencies: + object-assign "^4.1.1" + prop-types "^15.6.2" + react-is "^16.8.6" + scheduler "^0.13.6" + +react@^16.8.6: + version "16.8.6" + resolved "https://registry.npmjs.org/react/-/react-16.8.6.tgz#ad6c3a9614fd3a4e9ef51117f54d888da01f2bbe" + integrity sha512-pC0uMkhLaHm11ZSJULfOBqV4tIZkx87ZLvbbQYunNixAAvjnC+snJCg0XQXn9VIsttVsbZP/H/ewzgsd5fxKXw== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" prop-types "^15.6.2" - scheduler "^0.13.5" + scheduler "^0.13.6" read-cmd-shim@^1.0.1: version "1.0.1" @@ -7878,7 +8051,7 @@ regexpp@^2.0.1: resolved "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== -regexpu-core@^4.1.3, regexpu-core@^4.5.4: +regexpu-core@^4.5.4: version "4.5.4" resolved "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.5.4.tgz#080d9d02289aa87fe1667a4f5136bc98a6aebaae" integrity sha512-BtizvGtFQKGPUcTy56o3nk1bGRp4SZOTYrDtGNlqCQufptV5IkkLN6Emw+yunAJjzf+C9FQFtvq7IoA3+oMYHQ== @@ -8008,6 +8181,11 @@ requireindex@^1.2.0: resolved "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz#3463cdb22ee151902635aa6c9535d4de9c2ef1ef" integrity sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww== +reserved-words@^0.1.2: + version "0.1.2" + resolved "https://registry.npmjs.org/reserved-words/-/reserved-words-0.1.2.tgz#00a0940f98cd501aeaaac316411d9adc52b31ab1" + integrity sha1-AKCUD5jNUBrqqsMWQR2a3FKzGrE= + resolve-cwd@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" @@ -8179,6 +8357,14 @@ scheduler@^0.13.5: loose-envify "^1.1.0" object-assign "^4.1.1" +scheduler@^0.13.6: + version "0.13.6" + resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.13.6.tgz#466a4ec332467b31a91b9bf74e5347072e4cd889" + integrity sha512-IWnObHt413ucAYKsD9J1QShUKkbKLQQHdxRyw73sw4FN26iWr3DY/H34xGPe4nmL1DwXyWmSWmMrA9TfQbE/XQ== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + scoped-regex@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/scoped-regex/-/scoped-regex-1.0.0.tgz#a346bb1acd4207ae70bd7c0c7ca9e566b6baddb8" @@ -8201,6 +8387,11 @@ semver@5.5.0: resolved "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" integrity sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA== +semver@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/semver/-/semver-6.0.0.tgz#05e359ee571e5ad7ed641a6eec1e547ba52dea65" + integrity sha512-0UewU+9rFapKFnlbirLi3byoOuhrSsli/z/ihNnvM24vgF+8sNBiI1LZPBSH9wJKUwaUbw+s3hToDLCXkrghrQ== + semver@~5.3.0: version "5.3.0" resolved "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" @@ -8923,10 +9114,10 @@ typedarray@^0.0.6: resolved "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@^3.3.4000, typescript@~3.3.4000: - version "3.3.4000" - resolved "https://registry.npmjs.org/typescript/-/typescript-3.3.4000.tgz#76b0f89cfdbf97827e1112d64f283f1151d6adf0" - integrity sha512-jjOcCZvpkl2+z7JFn0yBOoLQyLoIkNZAs/fYJkUG6VKy6zLPHJGfQJYFHzibB6GJaF/8QrcECtlQ5cpvRHSMEA== +typescript@^3.4.1, typescript@~3.4.1: + version "3.4.1" + resolved "https://registry.npmjs.org/typescript/-/typescript-3.4.1.tgz#b6691be11a881ffa9a05765a205cb7383f3b63c6" + integrity sha512-3NSMb2VzDQm8oBTLH6Nj55VVtUEpe/rgkIzMir0qVoLyjDZlnMBva0U6vDiV3IH+sl/Yu6oP5QwsAQtHPmDd2Q== uglify-js@^3.1.4: version "3.4.10" @@ -9025,7 +9216,7 @@ unzip-response@^2.0.1: resolved "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97" integrity sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c= -upath@^1.1.0: +upath@^1.1.1: version "1.1.2" resolved "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz#3db658600edaeeccbe6db5e684d67ee8c2acd068" integrity sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q== From 2f2fbe2f0cba8a07a4577a6b6f512bead020e4f4 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Tue, 2 Apr 2019 22:16:39 -0700 Subject: [PATCH 20/23] Add docs. --- docs/event.md | 150 ++++++++++++++++++++++++++++++++++++ packages/core/CHANGELOG.md | 20 +++++ packages/event/README.md | 16 +++- packages/event/package.json | 5 +- 4 files changed, 187 insertions(+), 4 deletions(-) create mode 100644 docs/event.md diff --git a/docs/event.md b/docs/event.md new file mode 100644 index 000000000..f10067643 --- /dev/null +++ b/docs/event.md @@ -0,0 +1,150 @@ +# Events + +A strict type-safe event system with multiple emitter patterns. + +## Installation + +``` +yarn add @boost/event +``` + +## Usage + +The event system is built around individual `Event` classes that can be instantiated in isolation, +register and unregister their own listeners, and emit values by executing each listener with +arguments. There are multiple [types of events](#types), so choose the best one for each use case. + +To begin using events, instantiate an `Event` with a unique name -- the name is purely for debugging +purposes. + +```ts +const event = new Event<[string]>('example'); +``` + +`Event`s utilize TypeScript generics for typing the arguments passed to listener functions. This can +be defined using a tuple or an array. + +```ts +// One argument of type number +new Event<[number]>('foo'); + +// Two arguments of type number and string +new Event<[number, string]>('bar'); + +// Three arguments with the last item being optional +new Event<[object, boolean, string?]>('baz'); + +// Array of any type or size +new Event('foo'); +``` + +### Registering Listeners + +Listeners are simply functions that can be registered to an event using `Event#listen`. The same +listener function reference will only be registered once. + +```ts +event.listen(listener); +``` + +A listener can also be registered to execute only once, using `Event#once`, regardless of how many +times the event has been emitted. + +```ts +event.once(listener); +``` + +### Unregistering Listeners + +A listener can be unregistered from an event using `Event#unlisten`. The same listener reference +used to register must also be used for unregistering. + +```ts +event.unlisten(listener); +``` + +### Emitting Events + +Emitting is the concept of executing all registered listeners with a set of arguments. This can be +achieved through the `Event#emit` method, which requires an array of values to pass to each listener +as arguments. + +```ts +event.emit(['abc']); +``` + +> The array values and its types should match the [generics defined](#usage) on the constructor. + +## Scopes + +TODO + +## Types + +There are 4 types of events that can be instantiated and emitted. + +### `Event` + +Standard event that executes listeners in the order they were registered. + +```ts +const event = new Event<[string, number]>('standard'); + +event.listen(listener); + +event.emit(['abc', 123]); +``` + +### `BailEvent` + +Like `Event` but can bail the execution loop early if a listener returns `false`. The `emit` method +will return `true` if a bail occurs. + +```ts +const event = new BailEvent<[object]>('bail'); + +// Will execute +event.listen(() => {}); + +// Will execute and bail +event.listen(() => false); + +// Will not execute +event.listen(() => {}); + +const bailed = event.emit([{ example: true }]); +``` + +### `ParallelEvent` + +Executes listeners in parallel and returns a promise with the result of all listeners. + +```ts +const event = new ParallelEvent<[]>('parallel'); + +event.listen(doHeavyProcess); +event.listen(doBackgroundJob); + +// Async/await +const result = await event.emit([]); + +// Promise +event.emit([]).then(result => {}); +``` + +### `WaterfallEvent` + +Executes each listener in order, passing the previous listeners return value as an argument to the +next listener. + +```ts +const event = new WaterfallEvent('waterfall'); + +event.listen(num => num * 2); +event.listen(num => num * 3); + +const result = event.emit(10); // 60 +``` + +> This event only accepts a single argument. The generic type should not be an array, as it types +> the only argument and the return type. diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 5a2b4bc4b..8c2da5e53 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,3 +1,23 @@ +# 1.11.0 + +#### 🚀 New + +- Added a new package, [@boost/event](https://www.npmjs.com/package/@boost/event), to provide a + type-safe static event system. The old event emitter is deprecated, so please migrate to the new + system! + - Added `onError`, `onRoutine`, `onRoutines`, `onStart`, `onStop`, `onTask`, and `onTasks` events + to `Console`. + - Added `onRoutine`, `onRoutines`,`onTask`, and `onTasks` events to `Executor`. + - Added `onCommand` and `onCommandData` events to `Routine`. + - Added `onFail`, `onPass`, `onRun`, and `onSkip` to `Task` and `Routine`. + - Added `onExit` to `Tool`. +- Tasks and Routines can be skipped during their run process if an `onRun` event listener returns + `false`. + +#### 🛠 Internal + +- Started writing [documentation using GitBook](https://milesj.gitbook.io/boost/). + # 1.10.1 - 2019-03-24 #### 🐞 Fixes diff --git a/packages/event/README.md b/packages/event/README.md index a98610e15..7cde5bfaa 100644 --- a/packages/event/README.md +++ b/packages/event/README.md @@ -1,5 +1,19 @@ -# Event +# Boost Event [![Build Status](https://travis-ci.org/milesj/boost.svg?branch=master)](https://travis-ci.org/milesj/boost) [![npm version](https://badge.fury.io/js/%40boost%event.svg)](https://www.npmjs.com/package/@boost/event) [![npm deps](https://david-dm.org/milesj/boost.svg?path=packages/event)](https://www.npmjs.com/package/@boost/event) + +A strict type-safe event system with multiple emitter patterns. + +## Installation + +``` +yarn add @boost/event +// Or +npm install @boost/event +``` + +## Documentation + +[https://milesj.gitbook.io/boost](https://milesj.gitbook.io/boost) diff --git a/packages/event/package.json b/packages/event/package.json index e986bea26..3d4e4a76c 100644 --- a/packages/event/package.json +++ b/packages/event/package.json @@ -2,7 +2,7 @@ "name": "@boost/event", "version": "0.0.0", "description": "A type-safe event system. Designed for Boost applications.", - "keywords": [], + "keywords": ["boost", "event", "emitter", "type-safe"], "main": "./lib/index.js", "types": "./lib/index.d.ts", "engines": { @@ -12,6 +12,5 @@ "license": "MIT", "publishConfig": { "access": "public" - }, - "priority": 100 + } } From 267b5a43cf8c7e24c6eb05fa7fc63cd9c761ceeb Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Tue, 2 Apr 2019 22:17:59 -0700 Subject: [PATCH 21/23] Add imports to examples. --- docs/event.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/event.md b/docs/event.md index f10067643..81fce460a 100644 --- a/docs/event.md +++ b/docs/event.md @@ -18,6 +18,8 @@ To begin using events, instantiate an `Event` with a unique name -- the name is purposes. ```ts +import { Event } from '@boost/event'; + const event = new Event<[string]>('example'); ``` @@ -88,6 +90,8 @@ There are 4 types of events that can be instantiated and emitted. Standard event that executes listeners in the order they were registered. ```ts +import { Event } from '@boost/event'; + const event = new Event<[string, number]>('standard'); event.listen(listener); @@ -101,6 +105,8 @@ Like `Event` but can bail the execution loop early if a listener returns `false` will return `true` if a bail occurs. ```ts +import { BailEvent } from '@boost/event'; + const event = new BailEvent<[object]>('bail'); // Will execute @@ -120,6 +126,8 @@ const bailed = event.emit([{ example: true }]); Executes listeners in parallel and returns a promise with the result of all listeners. ```ts +import { ParallelEvent } from '@boost/event'; + const event = new ParallelEvent<[]>('parallel'); event.listen(doHeavyProcess); @@ -138,6 +146,8 @@ Executes each listener in order, passing the previous listeners return value as next listener. ```ts +import { WaterfallEvent } from '@boost/event'; + const event = new WaterfallEvent('waterfall'); event.listen(num => num * 2); From 25c301afcbb131ac448d4cf0d771637ace18734f Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Tue, 2 Apr 2019 23:03:33 -0700 Subject: [PATCH 22/23] Add scopes to docs. --- docs/event.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/docs/event.md b/docs/event.md index 81fce460a..f755977de 100644 --- a/docs/event.md +++ b/docs/event.md @@ -77,9 +77,22 @@ event.emit(['abc']); > The array values and its types should match the [generics defined](#usage) on the constructor. -## Scopes +### Scopes -TODO +Scopes are a mechanism for restricting listeners to a unique subset. Scopes are defined as the 2nd +argument to `Event#listen`, `unlisten`, `once`, and `emit`. + +```ts +event.listen(listener); +event.listen(listener, 'foo'); +event.listen(listener, 'bar'); + +// Will only execute the 1st listener +event.emit([]); + +// Will only execute the 2nd listener +event.emit([], 'foo'); +``` ## Types From cb536452db19ce6a837d50e1f247b3109f5a078c Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Tue, 2 Apr 2019 23:08:15 -0700 Subject: [PATCH 23/23] Rename ParallelEvent to ConcurrentEvent. --- docs/event.md | 6 +++--- packages/event/CHANGELOG.md | 2 +- .../event/src/{ParallelEvent.ts => ConcurrentEvent.ts} | 2 +- packages/event/src/index.ts | 4 ++-- .../{ParallelEvent.test.ts => ConcurrentEvent.test.ts} | 8 ++++---- packages/event/tests/typings.ts | 4 ++-- 6 files changed, 13 insertions(+), 13 deletions(-) rename packages/event/src/{ParallelEvent.ts => ConcurrentEvent.ts} (83%) rename packages/event/tests/{ParallelEvent.test.ts => ConcurrentEvent.test.ts} (85%) diff --git a/docs/event.md b/docs/event.md index f755977de..46cf58337 100644 --- a/docs/event.md +++ b/docs/event.md @@ -134,14 +134,14 @@ event.listen(() => {}); const bailed = event.emit([{ example: true }]); ``` -### `ParallelEvent` +### `ConcurrentEvent` Executes listeners in parallel and returns a promise with the result of all listeners. ```ts -import { ParallelEvent } from '@boost/event'; +import { ConcurrentEvent } from '@boost/event'; -const event = new ParallelEvent<[]>('parallel'); +const event = new ConcurrentEvent<[]>('parallel'); event.listen(doHeavyProcess); event.listen(doBackgroundJob); diff --git a/packages/event/CHANGELOG.md b/packages/event/CHANGELOG.md index 773872330..2b7205890 100644 --- a/packages/event/CHANGELOG.md +++ b/packages/event/CHANGELOG.md @@ -8,7 +8,7 @@ - Added `Event`, which synchronously fires listeners. - Added `BailEvent`, which will bail the loop if a listener returns `false`. -- Added `ParallelEvent`, which asynchronously fires listeners and return a promise. +- Added `ConcurrentEvent`, which asynchronously fires listeners and return a promise. - Added `WaterfallEvent`, which passes the return value to each listener. #### 🛠 Internals diff --git a/packages/event/src/ParallelEvent.ts b/packages/event/src/ConcurrentEvent.ts similarity index 83% rename from packages/event/src/ParallelEvent.ts rename to packages/event/src/ConcurrentEvent.ts index e73e13b7f..14895aea6 100644 --- a/packages/event/src/ParallelEvent.ts +++ b/packages/event/src/ConcurrentEvent.ts @@ -1,7 +1,7 @@ import BaseEvent from './BaseEvent'; import { Scope } from './types'; -export default class ParallelEvent extends BaseEvent< +export default class ConcurrentEvent extends BaseEvent< Args, Promise > { diff --git a/packages/event/src/index.ts b/packages/event/src/index.ts index 08e1da3c6..8f918d86a 100644 --- a/packages/event/src/index.ts +++ b/packages/event/src/index.ts @@ -6,7 +6,7 @@ import BailEvent from './BailEvent'; import BaseEvent from './BaseEvent'; import Event from './Event'; -import ParallelEvent from './ParallelEvent'; +import ConcurrentEvent from './ConcurrentEvent'; import WaterfallEvent from './WaterfallEvent'; export * from './constants'; @@ -14,4 +14,4 @@ export * from './constants'; // eslint-disable-next-line import/export export * from './types'; -export { BailEvent, BaseEvent, Event, ParallelEvent, WaterfallEvent }; +export { BailEvent, BaseEvent, Event, ConcurrentEvent, WaterfallEvent }; diff --git a/packages/event/tests/ParallelEvent.test.ts b/packages/event/tests/ConcurrentEvent.test.ts similarity index 85% rename from packages/event/tests/ParallelEvent.test.ts rename to packages/event/tests/ConcurrentEvent.test.ts index 0190e3ed1..e15c00c24 100644 --- a/packages/event/tests/ParallelEvent.test.ts +++ b/packages/event/tests/ConcurrentEvent.test.ts @@ -1,10 +1,10 @@ -import ParallelEvent from '../src/ParallelEvent'; +import ConcurrentEvent from '../src/ConcurrentEvent'; -describe('ParallelEvent', () => { - let event: ParallelEvent<[number]>; +describe('ConcurrentEvent', () => { + let event: ConcurrentEvent<[number]>; beforeEach(() => { - event = new ParallelEvent('parallel.test'); + event = new ConcurrentEvent('parallel.test'); }); beforeEach(() => { diff --git a/packages/event/tests/typings.ts b/packages/event/tests/typings.ts index 7d4edc70e..54ea749f5 100644 --- a/packages/event/tests/typings.ts +++ b/packages/event/tests/typings.ts @@ -1,10 +1,10 @@ /* eslint-disable */ -import { Event, BailEvent, ParallelEvent, WaterfallEvent } from '../src'; +import { Event, BailEvent, ConcurrentEvent, WaterfallEvent } from '../src'; const foo = new Event<[number, string?]>('foo'); const bar = new BailEvent<[number, number, object]>('bar'); -const baz = new ParallelEvent('baz'); +const baz = new ConcurrentEvent('baz'); const qux = new WaterfallEvent('qux'); // VALID