From 5578e41cb6db67eb0123336e571edb8c3d0a9bfa Mon Sep 17 00:00:00 2001 From: Jose David Rodriguez Velasco Date: Fri, 21 Jun 2019 15:53:05 -0700 Subject: [PATCH] fix: refactor @best/agent using types (#161) * fix: agents * fix: refactor agent * feat: timeout on upload start and delete old code * fix: include latest types * fix: agent tests * fix: more types * wip: more types * fix: types for runner-remote and sample config * fix: remove types in package.json * fix: minor types and styling --- packages/@best/agent/package.json | 6 +- packages/@best/agent/src/AgentApp.ts | 50 ++++ packages/@best/agent/src/BenchmarkRunner.ts | 153 ++++++++++++ packages/@best/agent/src/BenchmarkTask.ts | 37 +++ .../agent/src/__tests__/AgentApp.spec.ts | 225 ++++++++++++++++++ packages/@best/agent/src/agent-service.ts | 25 +- packages/@best/agent/src/benchmark-loader.ts | 38 +++ packages/@best/agent/src/broker.ts | 62 ----- packages/@best/agent/src/cli/index.ts | 17 +- packages/@best/agent/src/client.ts | 161 ------------- packages/@best/agent/src/operations.ts | 4 - packages/@best/agent/src/task.ts | 86 ------- .../@best/agent/src/utils/ObservableQueue.ts | 33 +++ packages/@best/agent/tsconfig.json | 5 +- .../@best/agent/typings/socket.ios-file.d.ts | 1 + packages/@best/config/src/utils/normalize.ts | 30 ++- packages/@best/runner-headless/tsconfig.json | 3 +- packages/@best/runner-remote/package.json | 5 +- packages/@best/runner-remote/src/index.ts | 51 ++-- packages/@best/runner-remote/tsconfig.json | 8 +- packages/@best/types/src/config.ts | 2 +- packages/best-benchmarks/best.config.js | 15 ++ yarn.lock | 109 ++++----- 23 files changed, 682 insertions(+), 444 deletions(-) create mode 100644 packages/@best/agent/src/AgentApp.ts create mode 100644 packages/@best/agent/src/BenchmarkRunner.ts create mode 100644 packages/@best/agent/src/BenchmarkTask.ts create mode 100644 packages/@best/agent/src/__tests__/AgentApp.spec.ts create mode 100644 packages/@best/agent/src/benchmark-loader.ts delete mode 100644 packages/@best/agent/src/broker.ts delete mode 100644 packages/@best/agent/src/client.ts delete mode 100644 packages/@best/agent/src/operations.ts delete mode 100644 packages/@best/agent/src/task.ts create mode 100644 packages/@best/agent/src/utils/ObservableQueue.ts diff --git a/packages/@best/agent/package.json b/packages/@best/agent/package.json index 39525a88..b0df2d00 100644 --- a/packages/@best/agent/package.json +++ b/packages/@best/agent/package.json @@ -12,8 +12,8 @@ "@best/utils": "4.0.0", "chalk": "~2.4.2", "express": "~4.16.2", - "socket.io": "~2.0.4", - "socket.io-file": "~2.0.2", - "tar": "~4.2.0" + "socket.io": "~2.2.0", + "socket.io-file": "~2.0.31", + "tar": "~4.4.10" } } diff --git a/packages/@best/agent/src/AgentApp.ts b/packages/@best/agent/src/AgentApp.ts new file mode 100644 index 00000000..17555dee --- /dev/null +++ b/packages/@best/agent/src/AgentApp.ts @@ -0,0 +1,50 @@ +import * as SocketIO from "socket.io"; +import ObservableQueue from "./utils/ObservableQueue"; +import BenchmarkRunner, { RunnerStatus } from "./BenchmarkRunner"; +import BenchmarkTask from "./BenchmarkTask"; +import { BuildConfig } from "@best/types"; + +export class AgentApp { + private queue: ObservableQueue; + private runner: BenchmarkRunner; + + constructor(queue: ObservableQueue, runner: BenchmarkRunner) { + this.queue = queue; + this.runner = runner; + + this.initializeHandlers(); + } + + private initializeHandlers() { + this.queue.on('item-added', (task: BenchmarkTask) => this.handleJobAddedInQueue(task)); + this.runner.on('idle-runner', (runner: BenchmarkRunner) => this.handleIdleRunner(runner)); + } + + handleIncomingConnection(socket: SocketIO.Socket) { + socket.on('benchmark_task', (data: BuildConfig) => { + const task = new BenchmarkTask(data, socket); + + socket.on('disconnect', () => { + this.queue.remove(task); + this.runner.cancelRun(task); + }); + + this.queue.push(task); + }); + } + + private handleJobAddedInQueue(task: BenchmarkTask) { + if (this.runner.status === RunnerStatus.IDLE) { + this.queue.remove(task); + this.runner.run(task); + } else { + task.socketConnection.emit('benchmark_enqueued', { pending: this.queue.size }); + } + } + + private handleIdleRunner(runner: BenchmarkRunner) { + if (this.queue.size > 0) { + runner.run(this.queue.pop()!); + } + } +} diff --git a/packages/@best/agent/src/BenchmarkRunner.ts b/packages/@best/agent/src/BenchmarkRunner.ts new file mode 100644 index 00000000..900a0eaf --- /dev/null +++ b/packages/@best/agent/src/BenchmarkRunner.ts @@ -0,0 +1,153 @@ +import path from 'path'; +import { EventEmitter } from "events"; +import { runBenchmark } from '@best/runner'; +import BenchmarkTask from "./BenchmarkTask"; +import { loadBenchmarkJob } from "./benchmark-loader"; +import { x as extractTar } from 'tar'; +import * as SocketIO from "socket.io"; +import { RunnerOutputStream } from "@best/console-stream"; +import { + BenchmarkResultsSnapshot, + BenchmarkResultsState, + BenchmarkRuntimeConfig +} from "@best/types"; + +export enum RunnerStatus { + IDLE = 1, + RUNNING, +} + +// @todo: make a Runner Stream, and add an interface type instead of the class. +function initializeForwarder(socket: SocketIO.Socket, logger: Function): RunnerOutputStream { + return { + init() {}, + finish() {}, + onBenchmarkStart(benchmarkPath: string) { + if (socket.connected) { + logger(`STATUS: running_benchmark ${benchmarkPath}`); + socket.emit('running_benchmark_start', benchmarkPath); + } + }, + onBenchmarkEnd(benchmarkPath: string) { + if (socket.connected) { + logger(`STATUS: finished_benchmark ${benchmarkPath}`); + socket.emit('running_benchmark_end', benchmarkPath); + } + }, + onBenchmarkError(benchmarkPath: string) { + if (socket.connected) { + socket.emit('running_benchmark_error', benchmarkPath); + } + }, + updateBenchmarkProgress(state: BenchmarkResultsState, opts: BenchmarkRuntimeConfig) { + if (socket.connected) { + socket.emit('running_benchmark_update', {state, opts}); + } + }, + } as RunnerOutputStream; +} + +function extractBenchmarkTarFile(task: BenchmarkTask) { + return ({ uploadDir } : { uploadDir: string }) => { + const benchmarkName = task.benchmarkName; + const benchmarkDirname = path.dirname(uploadDir); + + task.benchmarkFolder = benchmarkDirname; + task.benchmarkEntry = path.join(benchmarkDirname, `${benchmarkName}.html`); + + return extractTar({cwd: benchmarkDirname, file: uploadDir}); + }; +} + +export default class BenchmarkRunner extends EventEmitter { + public _status: RunnerStatus = RunnerStatus.IDLE; + public runningTask: BenchmarkTask | null = null; + public runningWasCancelled = false; + private _log: Function = () => {}; + + get status() { + return this._status; + } + + set status(value: RunnerStatus) { + if (value !== this._status) { + this._status = value; + if (value === RunnerStatus.IDLE) { + this.emit('idle-runner', this); + } + } + } + + cancelRun(task: BenchmarkTask) { + if (this.runningTask === task) { + this._log('Running was cancelled.'); + this.runningWasCancelled = true; + } + } + + run(task: BenchmarkTask) { + if (this.status !== RunnerStatus.IDLE) { + throw new Error("Trying to run a new benchmark while runner is busy"); + } + + this.status = RunnerStatus.RUNNING; + this.runningWasCancelled = false; + this.runningTask = task; + this._log = (msg: string) => { + if (!this.runningWasCancelled) { + process.stdout.write(`Task[${task.socketConnection.id}] - ${msg}\n`); + } + }; + + // @todo: just to be safe, add timeout in cancel so it waits for the runner to finish or dismiss the run assuming something went wrong + loadBenchmarkJob(task.socketConnection) + .then(extractBenchmarkTarFile(task)) + .then(() => this.runBenchmark(task)) + .then(({ error, results }: {error: any, results: any}) => { + this.afterRunBenchmark(error, results); + }) + .catch((err: any) => { + this.afterRunBenchmark(err, null); + }) + } + + private async runBenchmark(task: BenchmarkTask) { + const { benchmarkName } = task; + const messenger = initializeForwarder(task.socketConnection, this._log); + + let results; + let error; + + try { + this._log(`Running benchmark ${benchmarkName}`); + + results = await runBenchmark(task.config, messenger); + + this._log(`Benchmark ${benchmarkName} completed successfully`); + } catch (err) { + this._log(`Something went wrong while running ${benchmarkName}`); + process.stderr.write(err + '\n'); + error = err; + } + + return { error, results } + } + + private afterRunBenchmark(err: any, results: BenchmarkResultsSnapshot | null) { + if (!this.runningWasCancelled) { + this._log(`Sending results to client`); + + if (err) { + this._log(`Sending error`); + this.runningTask!.socketConnection.emit('benchmark_error', err.toString()); + } else { + this._log(`Sending results`); + this.runningTask!.socketConnection.emit('benchmark_results', results); + } + } + + this.runningWasCancelled = false; + this.runningTask = null; + this.status = RunnerStatus.IDLE; + } +} diff --git a/packages/@best/agent/src/BenchmarkTask.ts b/packages/@best/agent/src/BenchmarkTask.ts new file mode 100644 index 00000000..4238e716 --- /dev/null +++ b/packages/@best/agent/src/BenchmarkTask.ts @@ -0,0 +1,37 @@ +import * as SocketIO from "socket.io"; +import { BuildConfig, GlobalConfig, ProjectConfig } from "@best/types"; + +export default class BenchmarkTask { + public projectConfig: ProjectConfig; + public globalConfig: GlobalConfig; + public socketConnection: SocketIO.Socket; + private readonly _taskConfig: BuildConfig; + + constructor(taskConfig: BuildConfig, socket: SocketIO.Socket) { + this._taskConfig = taskConfig; + + this.projectConfig = taskConfig.projectConfig; + this.globalConfig = taskConfig.globalConfig; + this.socketConnection = socket; + } + + get config() { + return this._taskConfig; + } + + get benchmarkName() { + return this._taskConfig.benchmarkName; + } + + get benchmarkSignature() { + return this._taskConfig.benchmarkSignature; + } + + set benchmarkEntry(value: string) { + this._taskConfig.benchmarkEntry = value; + } + + set benchmarkFolder(value: string) { + this._taskConfig.benchmarkFolder = value; + } +} diff --git a/packages/@best/agent/src/__tests__/AgentApp.spec.ts b/packages/@best/agent/src/__tests__/AgentApp.spec.ts new file mode 100644 index 00000000..8f99b907 --- /dev/null +++ b/packages/@best/agent/src/__tests__/AgentApp.spec.ts @@ -0,0 +1,225 @@ +import ObservableQueue from "../utils/ObservableQueue"; +import BenchmarkTask from "../BenchmarkTask"; +import { AgentApp } from "../AgentApp"; +import BenchmarkRunner, {RunnerStatus} from "../BenchmarkRunner"; +import * as SocketIO from "socket.io"; +import { EventEmitter } from "events"; +import { FrozenGlobalConfig, FrozenProjectConfig } from "@best/types"; + +const createTask = (idx: number) => { + const SocketMock = jest.fn(); + const socket = new SocketMock(); + + const ProjectConfigMock = jest.fn(); + const GlobalConfigMock = jest.fn(); + + return new BenchmarkTask( + { + benchmarkName: 'name' + idx, + benchmarkEntry: 'entry' + idx, + benchmarkFolder: 'folder' + idx, + benchmarkSignature: 'signature' + idx, + projectConfig: new ProjectConfigMock(), + globalConfig: new GlobalConfigMock(), + }, + socket + ); +}; + +describe('Agent app', () => { + test('subscribes to queue.item-added and runner.idle-runner', async () => { + const queue = new ObservableQueue(); + const queueOnSpy = jest.spyOn(queue, 'on'); + + const runner = new BenchmarkRunner(); + const runnerOnSpy = jest.spyOn(runner, 'on'); + + const agentApp = new AgentApp(queue, runner); + + expect(queueOnSpy).toHaveBeenCalled(); + expect(queueOnSpy.mock.calls[0][0]).toBe('item-added'); + + expect(runnerOnSpy).toHaveBeenCalled(); + expect(runnerOnSpy.mock.calls[0][0]).toBe('idle-runner'); + }); + + describe('adding items to the queue', () => { + test('runs task when queue is empty', async () => { + const queue = new ObservableQueue(); + const queueRemoveSpy = jest.spyOn(queue, 'remove'); + + const runner = new BenchmarkRunner(); + runner.run = jest.fn(); + + const runnerRunSpy = jest.spyOn(runner, 'run'); + + const agentApp = new AgentApp(queue, runner); + + const task = createTask(1); + + queue.push(task); + + expect(runner.run).toHaveBeenCalled(); + expect(runnerRunSpy.mock.calls[0][0]).toBe(task); + + expect(queueRemoveSpy).toHaveBeenCalled(); + expect(runnerRunSpy.mock.calls[0][0]).toBe(task); + }); + + test('if runner is running a task informs client that the job is added to the queue', async () => { + const queue = new ObservableQueue(); + + const runner = new BenchmarkRunner(); + runner.status = RunnerStatus.RUNNING; + runner.run = jest.fn(); + const runnerRunSpy = jest.spyOn(runner, 'run'); + + const agentApp = new AgentApp(queue, runner); + + const task = createTask(1); + task.socketConnection.emit = jest.fn(); + const socketEmitSpy = jest.spyOn(task.socketConnection, 'emit'); + + queue.push(task); + + expect(runnerRunSpy).not.toHaveBeenCalled(); + + expect(socketEmitSpy).toHaveBeenCalled(); + expect(socketEmitSpy.mock.calls[0][0]).toBe('benchmark_enqueued'); + expect(socketEmitSpy.mock.calls[0][1]).toEqual({ pending: 1 }); + }); + }); + + describe('runner becomes idle', () => { + test('runs the next task in the queue', async () => { + const queue = new ObservableQueue(); + + const task1 = createTask(1); + const task2 = createTask(2); + + queue.push(task1); + queue.push(task2); + + const runner = new BenchmarkRunner(); + runner.status = RunnerStatus.RUNNING; + runner.run = jest.fn(); + const runnerRunSpy = jest.spyOn(runner, 'run'); + + const agentApp = new AgentApp(queue, runner); + + runner.status = RunnerStatus.IDLE; + + expect(runnerRunSpy).toHaveBeenCalled(); + expect(runnerRunSpy.mock.calls[0][0]).toBe(task1); + + expect(queue.size).toBe(1); + }); + }) + + describe('incoming socket connection', () => { + test('listen for benchmark_task event', async () => { + const queue = new ObservableQueue(); + const runner = new BenchmarkRunner(); + + const agentApp = new AgentApp(queue, runner); + + const SocketMock = jest.fn(); + const socket = new SocketMock(); + + socket.on = jest.fn(); + const socketOnSpy = jest.spyOn(socket, 'on'); + + agentApp.handleIncomingConnection(socket); + + expect(socketOnSpy).toHaveBeenCalled(); + expect(socketOnSpy.mock.calls[0][0]).toBe('benchmark_task'); + }); + + test('on benchmark_task adds task to the queue and listens for disconnect event on socket', async () => { + const queue = new ObservableQueue(); + queue.push = jest.fn(); + const queuePushSpy = jest.spyOn(queue, 'push'); + + const runner = new BenchmarkRunner(); + + const agentApp = new AgentApp(queue, runner); + + const SocketMock = jest.fn(); + const socket = new SocketMock(); + + const eventEmitter = new EventEmitter(); + socket.on = jest.fn((evt: string, fn: (...args: any[]) => void): SocketIO.Socket => { + eventEmitter.on(evt, fn); + return socket; + }); + + const socketOnSpy = jest.spyOn(socket, 'on'); + + agentApp.handleIncomingConnection(socket); + + socketOnSpy.mockReset(); + + eventEmitter.emit('benchmark_task', { + benchmarkName: 'name', + benchmarkSignature: 'signature', + projectConfig: 'project-config', + globalConfig: 'global-config', + socket + }); + + expect(socketOnSpy).toHaveBeenCalled(); + expect(socketOnSpy.mock.calls[0][0]).toBe('disconnect'); + + expect(queuePushSpy).toHaveBeenCalled(); + const task: BenchmarkTask = queuePushSpy.mock.calls[0][0]; + expect(task.benchmarkName).toBe('name'); + expect(task.benchmarkSignature).toBe('signature'); + expect(task.globalConfig).toBe('global-config'); + expect(task.projectConfig).toBe('project-config'); + expect(task.socketConnection).toBe(socket); + }); + + test('on socket.disconnect removes task from queue and runner', async () => { + const queue = new ObservableQueue(); + queue.push = jest.fn(); + queue.remove = jest.fn(); + const queueRemoveSpy = jest.spyOn(queue, 'remove'); + + const runner = new BenchmarkRunner(); + runner.cancelRun = jest.fn(); + const runnerCancelRunSpy = jest.spyOn(runner, 'cancelRun'); + + const agentApp = new AgentApp(queue, runner); + + const SocketMock = jest.fn(); + const socket = new SocketMock(); + + const eventEmitter = new EventEmitter(); + socket.on = jest.fn((evt: string, fn: (...args: any[]) => void): SocketIO.Socket => { + eventEmitter.on(evt, fn); + return socket; + }); + + agentApp.handleIncomingConnection(socket); + + eventEmitter.emit('benchmark_task', { + benchmarkName: 'name', + benchmarkSignature: 'signature', + projectConfig: 'project-config', + globalConfig: 'global-config', + socket + }); + + eventEmitter.emit('disconnect'); + + expect(queueRemoveSpy).toHaveBeenCalled(); + const task = queueRemoveSpy.mock.calls[0][0]; + + expect(task.socketConnection).toBe(socket); + + const taskFromRunner = runnerCancelRunSpy.mock.calls[0][0]; + expect(taskFromRunner.socketConnection).toBe(socket); + }); + }) +}); + diff --git a/packages/@best/agent/src/agent-service.ts b/packages/@best/agent/src/agent-service.ts index 76fb0cb7..fb4f895d 100644 --- a/packages/@best/agent/src/agent-service.ts +++ b/packages/@best/agent/src/agent-service.ts @@ -1,18 +1,19 @@ import socketIO from 'socket.io'; -import AgentBroker from './broker'; +import * as SocketIO from "socket.io"; +import { AgentApp } from "./AgentApp"; +import ObservableQueue from "./utils/ObservableQueue"; +import BenchmarkTask from "./BenchmarkTask"; +import BenchmarkRunner from "./BenchmarkRunner"; +import { Server } from "http"; -let BROKER: any; +export async function runAgent(server: Server) { + const socketServer: SocketIO.Server = socketIO(server, { path: '/best' }); -export async function runAgent(server: any) { - const socketServer = socketIO(server, { path: '/best' }); - BROKER = new AgentBroker(socketServer); -} + const taskQueue = new ObservableQueue(); + const taskRunner = new BenchmarkRunner(); + const agentApp: AgentApp = new AgentApp(taskQueue, taskRunner); -export async function reset() { - return BROKER.reset(); -} -export async function getState() { - return BROKER.getState(); + socketServer.on('connect', (socket: SocketIO.Socket) => agentApp.handleIncomingConnection(socket)); } -export default { runAgent, reset, getState }; +export default { runAgent }; diff --git a/packages/@best/agent/src/benchmark-loader.ts b/packages/@best/agent/src/benchmark-loader.ts new file mode 100644 index 00000000..04033674 --- /dev/null +++ b/packages/@best/agent/src/benchmark-loader.ts @@ -0,0 +1,38 @@ +import SocketIOFile from "socket.io-file"; +import path from "path"; +import { cacheDirectory } from '@best/utils'; +import * as SocketIO from "socket.io"; + +// This is all part of the initialization +const LOADER_CONFIG = { + uploadDir: path.join(cacheDirectory('best_agent'), 'uploads'), + accepts: [], + maxFileSize: 52428800, // 50 mb + chunkSize: 10240, // 10kb + transmissionDelay: 0, + overwrite: true, +}; + +const UPLOAD_START_TIMEOUT = 5000; + +export async function loadBenchmarkJob(socketConnection: SocketIO.Socket): Promise { + return new Promise(async (resolve, reject) => { + const socket = socketConnection; + let uploaderTimeout: any = null; + const uploader = new SocketIOFile(socket, LOADER_CONFIG); + + uploader.on('start', () => clearTimeout(uploaderTimeout)); + uploader.on('stream', ({ wrote, size }: any) => { + process.stdout.write(`Client[${socketConnection.id}] - downloading ${wrote} / ${size}\n`); + }); + uploader.on('complete', (info: any) => resolve(info)); + uploader.on('error', (err: any) => reject(err)); + + socket.emit('load_benchmark'); + uploaderTimeout = setTimeout(() => { + uploader.destroy(); + + reject(new Error(`Timed out waiting upload to start. Waited for ${UPLOAD_START_TIMEOUT}ms`)); + }, UPLOAD_START_TIMEOUT); + }); +} diff --git a/packages/@best/agent/src/broker.ts b/packages/@best/agent/src/broker.ts deleted file mode 100644 index d8bfd99c..00000000 --- a/packages/@best/agent/src/broker.ts +++ /dev/null @@ -1,62 +0,0 @@ -import SocketClient from './client'; -import BenchmarkTask from './task'; - -export default class Broker { - clients: WeakSet; - clientQueue: any; - runningTask: any; - socketServer: any; - constructor(socketServer: any) { - this.clients = new WeakSet(); - this.clientQueue = []; - this.runningTask = null; - this.socketServer = socketServer; - socketServer.on('connect', (socket: any) => this.setupConnection(socket)); - } - - isTaskRunning() { - return !!this.runningTask; - } - - setupConnection(socket: any) { - const client = new SocketClient(socket); - this.clients.add(client); - this.connectedClient(client); - client.on('disconnect', (reason) => this.disconnectedClient(client)); - } - - connectedClient(client: any) { - if (!this.isTaskRunning()) { - this.runClientTask(client); - } else { - this.clientQueue.push(client); - client.setEnqueued({ pending: this.clientQueue.length }); - } - } - - disconnectedClient(client: any) { - this.clientQueue = this.clientQueue.filter((c: any) => client !== c); - } - - runClientTask(client: any) { - const task = new BenchmarkTask(client); - task.on('complete', () => this.resetRunningTask()); - task.on('error', () => this.resetRunningTask()); - this.runningTask = task; - task.start(); - } - - runNextInQueue() { - const client = this.clientQueue.shift(); - if (client) { - this.runClientTask(client); - } - } - - resetRunningTask() { - this.runningTask = null; - this.runNextInQueue(); - } - - status() {} -} diff --git a/packages/@best/agent/src/cli/index.ts b/packages/@best/agent/src/cli/index.ts index 6eca9527..a1cf6f68 100644 --- a/packages/@best/agent/src/cli/index.ts +++ b/packages/@best/agent/src/cli/index.ts @@ -7,18 +7,15 @@ const SSL_PFX_PASSPHRASE = process.env.SSL_PFX_PASSPHRASE; export function run() { const app = express(); - let server; + const enableHttps = SSL_PFX_FILE && SSL_PFX_PASSPHRASE; + const http = require(enableHttps ? 'https' : 'http'); - if (SSL_PFX_FILE && SSL_PFX_PASSPHRASE) { - const options = { - pfx: readFileSync(SSL_PFX_FILE), - passphrase: SSL_PFX_PASSPHRASE - }; - server = require('https').createServer(options, app); - } else { - server = require('http').createServer(app); - } + const options = { + pfx: SSL_PFX_FILE ? readFileSync(SSL_PFX_FILE) : undefined, + passphrase: enableHttps ? SSL_PFX_PASSPHRASE: undefined + }; + const server = http.createServer(options, app); server.listen(PORT); app.get('/', (req, res) => res.send('BEST agent running!')); diff --git a/packages/@best/agent/src/client.ts b/packages/@best/agent/src/client.ts deleted file mode 100644 index dbcf86c6..00000000 --- a/packages/@best/agent/src/client.ts +++ /dev/null @@ -1,161 +0,0 @@ -import SocketIOFile from 'socket.io-file'; -import EventEmitter from 'events'; -import path from 'path'; -import { ERROR, BENCHMARK_TASK, DISCONNECT, LOAD_BENCHMARK } from './operations'; -import { cacheDirectory } from '@best/utils'; -import { x as extractTar } from 'tar'; - -const STATE_QUEUED = 'queued'; -const STATE_CONFIG_READY = 'config_ready'; -const STATE_DISCONNECTED = 'disconnected'; -const STATE_LOADING_FILES = 'loading_files'; -const STATE_BENCHMARK_READY = 'benchmark_ready'; -const STATE_RUNNING = 'benchmark_running'; -const STATE_COMPLETED = 'done'; - -const LOADER_CONFIG = { - uploadDir: path.join(cacheDirectory('best_agent'), 'uploads'), - accepts: [], - maxFileSize: 52428800, // 50 mb - chunkSize: 10240, // 10kb - transmissionDelay: 0, - overwrite: true, -}; - -function initializeForwarder(config: any, socket: any, logger: any) { - return { - onBenchmarkStart(benchmarkName: string, projectName: string) { - logger(`STATUS: running_benchmark ${benchmarkName} (${projectName})`); - socket.emit('running_benchmark_start', benchmarkName, projectName); - }, - updateBenchmarkProgress(state: any, opts: any) { - socket.emit('running_benchmark_update', { state, opts }); - }, - onBenchmarkEnd(benchmarkName: string, projectName: string) { - logger(`STATUS: finished_benchmark ${benchmarkName} (${projectName})`); - socket.emit('running_benchmark_end', benchmarkName, projectName); - }, - onBenchmarkError(benchmarkName: string, projectName: string) { - socket.emit('running_benchmark_error', benchmarkName, projectName); - }, - }; -} - -class SocketClient extends EventEmitter { - socket: any; - state: string; - benchmarkConfig: any; - _timeout: any; - static DISCONNECTED: string; - static CONFIG_READY: any; - static BENCHMARK_READY: any; - - constructor(socket: any) { - super(); - this.socket = socket; - this.state = 'init'; - this.benchmarkConfig = null; - socket.on(DISCONNECT, () => this.disconnectClient()); - socket.on(BENCHMARK_TASK, (data: any) => this.onBenchmarkTaskReceived(data)); - this.setTimeout(5000); - this._log('STATUS: connected'); - } - _log(msg: string) { - process.stdout.write(`Client[${this.socket.id}] - ${msg}\n`); - } - - setTimeout(t: number) { - this._timeout = setTimeout(() => this.disconnectClient('timeout - waiting for event'), t); - } - - getBenchmarkConfig() { - return this.benchmarkConfig; - } - setState(state: any) { - this._log(`STATUS: ${this.state} => ${state}`); - this.state = state; - this.socket.emit('state_change', state); - } - - disconnectClient(forcedError?: any) { - if (this.state !== STATE_DISCONNECTED) { - this.state = STATE_DISCONNECTED; - if (forcedError) { - clearTimeout(this._timeout); - this.socket.disconnect(true); - } - this._log(`STATUS: disconnected (${forcedError || 'socket disconnected'})`); - this.emit(DISCONNECT); - } - } - - onBenchmarkTaskReceived(benchmarkConfig: any) { - clearTimeout(this._timeout); - // Keys: { benchmarkName, benchmarkSignature, projectConfig, globalConfig } = benchmarkConfig; - this.benchmarkConfig = benchmarkConfig; - this.setState(STATE_CONFIG_READY); - this.emit(STATE_CONFIG_READY); - } - - hasBenchmarkConfig() { - return !!this.benchmarkConfig; - } - - onLoadedBenchmarks({ uploadDir }: any) { - extractTar({ cwd: path.dirname(uploadDir), file: uploadDir }).then(() => { - const benchmarkName = this.benchmarkConfig.benchmarkName; - const benchmarkDirname = path.dirname(uploadDir); - - this.benchmarkConfig.benchmarkEntry = path.join(benchmarkDirname, `${benchmarkName}.html`); - this.setState(STATE_BENCHMARK_READY); - - this._log(`STATUS: ${this.state} (${benchmarkName})`); - this.emit(STATE_BENCHMARK_READY); - }); - } - - onUploaderError(data: any) { - this.emit(ERROR, data); - } - - loadBenchmarks() { - const uploader = new SocketIOFile(this.socket, LOADER_CONFIG); - uploader.on('start', () => clearTimeout(this._timeout)); - uploader.on('stream', ({ wrote, size }: any) => this._log(`downloading ${wrote} / ${size}`)); - uploader.on('complete', (info: any) => this.onLoadedBenchmarks(info)); - uploader.on('error', (err: any) => this.onUploaderError(err)); - this.setState(STATE_LOADING_FILES); - this.socket.emit(LOAD_BENCHMARK); - this.setTimeout(5000); - } - - setEnqueued(status: any) { - this.socket.emit('benchmark_enqueued', status); - this.setState(STATE_QUEUED); - } - - setRunning() { - this.setState(STATE_RUNNING); - } - - sendBenchmarkResults(err: any, benchmarkResults: any) { - if (err) { - this._log(`Sending error`); - this.socket.emit('benchmark_error', err.toString()); - } else { - this._log(`Sending results`); - this.socket.emit('benchmark_results', benchmarkResults); - } - this.setState(STATE_COMPLETED); - } - - getMessager() { - return initializeForwarder(this.benchmarkConfig, this.socket, this._log.bind(this)); - } -} - -SocketClient.CONFIG_READY = STATE_CONFIG_READY; -SocketClient.BENCHMARK_READY = STATE_BENCHMARK_READY; -SocketClient.DISCONNECTED = STATE_DISCONNECTED; - -export default SocketClient; diff --git a/packages/@best/agent/src/operations.ts b/packages/@best/agent/src/operations.ts deleted file mode 100644 index c0fe5e13..00000000 --- a/packages/@best/agent/src/operations.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const BENCHMARK_TASK = 'benchmark_task'; -export const DISCONNECT = 'disconnect'; -export const LOAD_BENCHMARK = 'load_benchmark'; -export const ERROR = 'error'; diff --git a/packages/@best/agent/src/task.ts b/packages/@best/agent/src/task.ts deleted file mode 100644 index 15d4d560..00000000 --- a/packages/@best/agent/src/task.ts +++ /dev/null @@ -1,86 +0,0 @@ -import EventEmitter from 'events'; -import SocketClient from './client'; -import { runBenchmark } from '@best/runner'; -import { ERROR } from './operations'; - -const WAITING_FOR_CONFIG = 'waiting_for_config'; -const WAITING_FOR_BENCHMARK = 'waiting_for_benchmark'; -const WAITING_FOR_ABORT = 'waiting_for_abort'; -const RUNNING = 'running'; -const COMPLETED = 'complete'; - -let counter = 0; -export default class BenchmarkTask extends EventEmitter { - id: number; - state: string; - client: any; - constructor(client: any) { - super(); - this.id = ++counter; - this.state = WAITING_FOR_CONFIG; - this.client = client; - client.on(SocketClient.CONFIG_READY, () => this.onBenchmarkConfigReady()); - client.on(SocketClient.BENCHMARK_READY, () => this.onBenchmarksReady()); - client.on('disconnect', () => this.onClientDisconnected()); - client.on('error', (err: any) => this.onClientError(err)); - } - _log(msg: string) { - process.stdout.write(`Task[${this.id}] - ${msg}\n`); - } - - start() { - if (this.client.hasBenchmarkConfig()) { - this._log('Asking client for benchmark artifacts'); - this.state = WAITING_FOR_BENCHMARK; - this.client.loadBenchmarks(); - } - } - - onClientDisconnected() { - if (this.state === RUNNING) { - this._log('Aborting task. Client disconnected while running the task'); - this._log('Benchmark will finish before releasing the task'); - this.state = WAITING_FOR_ABORT; - } - } - - onClientError(err: any) { - this._log('Error running task:' + err.toString()); - // console.log('Stack trace: ', err); - this.emit(ERROR, err); - } - - onBenchmarkConfigReady() { - this.start(); - } - - onBenchmarksReady() { - this.client.setRunning(); - this.runBenchmarkTask(this.client.getBenchmarkConfig()); - } - - async runBenchmarkTask(benchmarkConfig: any) { - this.state = RUNNING; - const { benchmarkName } = benchmarkConfig; - let results; - let error; - try { - this._log(`Running benchmark ${benchmarkName}`); - results = await runBenchmark(benchmarkConfig, this.client.getMessager()); - this._log(`Benchmark ${benchmarkName} completed successfully`); - } catch (err) { - this._log(`Something went wrong while running ${benchmarkName}`); - process.stderr.write(err + '\n'); - error = err; - } finally { - this.afterRunBenchmark(error, results); - } - } - - afterRunBenchmark(error: any, results: any) { - this.state = COMPLETED; - this._log(`Sending results to client`); - this.client.sendBenchmarkResults(error, results); - this.emit(COMPLETED); - } -} diff --git a/packages/@best/agent/src/utils/ObservableQueue.ts b/packages/@best/agent/src/utils/ObservableQueue.ts new file mode 100644 index 00000000..45fc279b --- /dev/null +++ b/packages/@best/agent/src/utils/ObservableQueue.ts @@ -0,0 +1,33 @@ +import { EventEmitter } from "events"; + +export default class ObservableQueue extends EventEmitter { + _store: T[] = []; + + push(val: T) { + this._store.push(val); + this.emit('item-added', val); + } + + pop(): T | undefined { + const item = this._store.shift(); + + if (item !== undefined) { + this.emit('item-removed', item); + } + + return item; + } + + remove(val: T) { + for(let i = 0, n = this._store.length; i < n; i++){ + if (this._store[i] === val) { + this._store.splice(i, 1); + this.emit('item-removed', val); + } + } + } + + get size() { + return this._store.length; + } +} diff --git a/packages/@best/agent/tsconfig.json b/packages/@best/agent/tsconfig.json index 8ff8e771..d83c6a1b 100644 --- a/packages/@best/agent/tsconfig.json +++ b/packages/@best/agent/tsconfig.json @@ -2,10 +2,11 @@ "extends": "../../../tsconfig.settings.json", "compilerOptions": { "rootDir": "src", - "outDir": "build", + "outDir": "build" }, "references": [ { "path": "../runner" }, - { "path": "../utils" } + { "path": "../utils" }, + { "path": "../types" } ] } diff --git a/packages/@best/agent/typings/socket.ios-file.d.ts b/packages/@best/agent/typings/socket.ios-file.d.ts index f7e0b880..ab06e519 100644 --- a/packages/@best/agent/typings/socket.ios-file.d.ts +++ b/packages/@best/agent/typings/socket.ios-file.d.ts @@ -2,6 +2,7 @@ declare module 'socket.io-file' { export default class SocketIO { on(arg0: string, arg1: ({ wrote, size }: any) => void): void; + destroy(): void; constructor(socket: any, config: any); } } diff --git a/packages/@best/config/src/utils/normalize.ts b/packages/@best/config/src/utils/normalize.ts index 093d00d9..8c931e9a 100644 --- a/packages/@best/config/src/utils/normalize.ts +++ b/packages/@best/config/src/utils/normalize.ts @@ -11,11 +11,7 @@ function normalizeModulePathPatterns(options: any, key: string) { return options[key].map((pattern: any) => replacePathSepForRegex(normalizeRootDirPattern(pattern, options.rootDir))); } -function normalizeRunner(runner: string, runners?: RunnerConfig[]) { - if (!runners) { - return runner; - } - +function normalizeRunner(runner: string, runners: RunnerConfig[]) { const defaultRunners = runners.filter((c: RunnerConfig) => c.alias === undefined || c.alias === 'default'); if (defaultRunners.length > 1) { throw new Error('Wrong configuration: More than one default configuration declared'); @@ -37,6 +33,27 @@ function normalizeRunner(runner: string, runners?: RunnerConfig[]) { return selectedRunner.runner; } +function normalizeRunnerConfig(runner: string, runners?: RunnerConfig[]) { + if (!runners) { + return {}; + } + + if (runner === "default") { + const defaultRunners = runners.filter((c: RunnerConfig) => c.alias === undefined || c.alias === 'default'); + if (defaultRunners.length > 0) { + return defaultRunners[0].config ? defaultRunners[0].config : {}; + } + } + + const selectedRunner = runners.find((c: RunnerConfig) => c.alias === runner || c.runner === runner); + + if (!selectedRunner) { + throw new Error(`Unable to find a runner for ${runner}`); + } + + return selectedRunner ? selectedRunner.config : {}; +} + function setCliOptionOverrides(initialOptions: UserConfig, argsCLI: CliConfig): UserConfig { const argvToOptions = Object.keys(argsCLI) .reduce((options: any, key: string) => { @@ -143,6 +160,9 @@ export function normalizeConfig(userConfig: UserConfig, cliOptions: CliConfig): case 'runner': mergeConfig[key] = normalizeRunner(userCliMergedConfig[key], mergeConfig.runners); break; + case 'runnerConfig': + mergeConfig[key] = normalizeRunnerConfig(userCliMergedConfig['runner'], mergeConfig.runners); + break; case 'compareStats': mergeConfig[key] = normalizeCommits(userCliMergedConfig[key]); break; diff --git a/packages/@best/runner-headless/tsconfig.json b/packages/@best/runner-headless/tsconfig.json index 37c3bdce..90a5ade2 100644 --- a/packages/@best/runner-headless/tsconfig.json +++ b/packages/@best/runner-headless/tsconfig.json @@ -2,10 +2,11 @@ "extends": "../../../tsconfig.settings.json", "compilerOptions": { "rootDir": "src", - "outDir": "build", + "outDir": "build" }, "references": [ { "path": "../types" }, { "path": "../runner-abstract" }, + { "path": "../types" } ] } diff --git a/packages/@best/runner-remote/package.json b/packages/@best/runner-remote/package.json index bfee60d6..36a62bff 100644 --- a/packages/@best/runner-remote/package.json +++ b/packages/@best/runner-remote/package.json @@ -9,7 +9,8 @@ ], "main": "build/index.js", "dependencies": { - "socket.io-client": "~2.0.4", - "tar": "~4.2.0" + "@best/console-stream": "4.0.0", + "socket.io-client": "~2.2.0", + "tar": "~4.4.10" } } diff --git a/packages/@best/runner-remote/src/index.ts b/packages/@best/runner-remote/src/index.ts index 7a164eb1..7e1f45eb 100644 --- a/packages/@best/runner-remote/src/index.ts +++ b/packages/@best/runner-remote/src/index.ts @@ -3,11 +3,20 @@ import fs from 'fs'; import socketIO from 'socket.io-client'; import SocketIOFile from './file-uploader'; import { createTarBundle } from './create-tar'; - -function proxifyRunner(benchmarkEntryBundle: any, runnerConfig: any, projectConfig: any, globalConfig: any, messager: any) { +import { RunnerOutputStream } from "@best/console-stream"; +import { + BenchmarkInfo, + BenchmarkResultsSnapshot, + BenchmarkResultsState, + BenchmarkRuntimeConfig, + FrozenGlobalConfig, + FrozenProjectConfig +} from "@best/types"; + +function proxifyRunner(benchmarkEntryBundle: BenchmarkInfo, projectConfig: FrozenProjectConfig, globalConfig: FrozenGlobalConfig, messager: RunnerOutputStream) : Promise { return new Promise(async (resolve, reject) => { const { benchmarkName, benchmarkEntry, benchmarkSignature } = benchmarkEntryBundle; - const { host, options, remoteRunner } = runnerConfig; + const { host, options, remoteRunner } = projectConfig.benchmarkRunnerConfig; const bundleDirname = path.dirname(benchmarkEntry); const remoteProjectConfig = Object.assign({}, projectConfig, { benchmarkRunner: remoteRunner, @@ -20,12 +29,9 @@ function proxifyRunner(benchmarkEntryBundle: any, runnerConfig: any, projectConf return reject(new Error('Benchmark artifact not found (${tarBundle})')); } - // preRunMessager.print(`Attempting connection with agent at ${host} ...`, process.stdout); const socket = socketIO(host, options); socket.on('connect', () => { - // preRunMessager.clear(process.stdout); - socket.on('load_benchmark', () => { const uploader = new SocketIOFile(socket); uploader.on('ready', () => { @@ -37,22 +43,20 @@ function proxifyRunner(benchmarkEntryBundle: any, runnerConfig: any, projectConf }); }); - socket.on('running_benchmark_start', (benchName: string, projectName: string) => { - messager.logState(`Running benchmarks remotely...`); - messager.onBenchmarkStart(benchName, projectName, { - displayPath: `${host}/${benchName}`, - }); + socket.on('running_benchmark_start', () => { + messager.log(`Running benchmarks remotely...`); + messager.onBenchmarkStart(benchmarkEntry); }); - socket.on('running_benchmark_update', ({ state, opts }: any) => { + socket.on('running_benchmark_update', ({ state, opts }: { state: BenchmarkResultsState, opts: BenchmarkRuntimeConfig }) => { messager.updateBenchmarkProgress(state, opts); }); - socket.on('running_benchmark_end', (benchName: string, projectName: string) => { - messager.onBenchmarkEnd(benchName, projectName); + socket.on('running_benchmark_end', () => { + messager.onBenchmarkEnd(benchmarkEntry); }); - socket.on('benchmark_enqueued', ({ pending }: any) => { - messager.logState(`Queued in agent. Pending tasks: ${pending}`); + socket.on('benchmark_enqueued', ({ pending }: { pending: number }) => { + messager.log(`Queued in agent. Pending tasks: ${pending}`); }); socket.on('disconnect', (reason: string) => { @@ -61,12 +65,8 @@ function proxifyRunner(benchmarkEntryBundle: any, runnerConfig: any, projectConf } }); - // socket.on('state_change', (s) => { - // console.log('>> State change', s); - // }); - socket.on('error', (err: any) => { - console.log('> ', err); + console.log('Error in connection to agent > ', err); reject(err); }); @@ -75,9 +75,9 @@ function proxifyRunner(benchmarkEntryBundle: any, runnerConfig: any, projectConf reject(new Error('Benchmark couldn\'t finish running. ')); }); - socket.on('benchmark_results', ({ results, environment }: any) => { + socket.on('benchmark_results', (result: BenchmarkResultsSnapshot) => { socket.disconnect(); - resolve({ results, environment }); + resolve(result); }); socket.emit('benchmark_task', { @@ -93,8 +93,7 @@ function proxifyRunner(benchmarkEntryBundle: any, runnerConfig: any, projectConf } export class Runner { - run(benchmarkEntryBundle: any, projectConfig: any, globalConfig: any, messager: any) { - const { benchmarkRunnerConfig } = projectConfig; - return proxifyRunner(benchmarkEntryBundle, benchmarkRunnerConfig, projectConfig, globalConfig, messager); + run(benchmarkInfo: BenchmarkInfo, projectConfig: FrozenProjectConfig, globalConfig: FrozenGlobalConfig, runnerLogStream: RunnerOutputStream): Promise { + return proxifyRunner(benchmarkInfo, projectConfig, globalConfig, runnerLogStream); } } diff --git a/packages/@best/runner-remote/tsconfig.json b/packages/@best/runner-remote/tsconfig.json index b8635ca4..03e1f37e 100644 --- a/packages/@best/runner-remote/tsconfig.json +++ b/packages/@best/runner-remote/tsconfig.json @@ -2,6 +2,10 @@ "extends": "../../../tsconfig.settings.json", "compilerOptions": { "rootDir": "src", - "outDir": "build", - } + "outDir": "build" + }, + "references": [ + { "path": "../console-stream" }, + { "path": "../types" } + ] } diff --git a/packages/@best/types/src/config.ts b/packages/@best/types/src/config.ts index 0d9e3de2..de67cc9a 100644 --- a/packages/@best/types/src/config.ts +++ b/packages/@best/types/src/config.ts @@ -63,7 +63,7 @@ export interface NormalizedConfig { projects: string[], plugins: ProjectConfigPlugin[], runner: string, - runners?: RunnerConfig[], + runners: RunnerConfig[], runnerConfig: any, benchmarkEnvironment: string, benchmarkEnvironmentOptions: {[key:string]: string }, diff --git a/packages/best-benchmarks/best.config.js b/packages/best-benchmarks/best.config.js index f771b30c..5a9e92e4 100644 --- a/packages/best-benchmarks/best.config.js +++ b/packages/best-benchmarks/best.config.js @@ -1,3 +1,18 @@ module.exports = { projectName: 'best-benchmark', + "runners": [ + { + "runner": "@best/runner-headless", + "alias": "default" + }, + { + "runner": "@best/runner-remote", + "alias": "remote-agent", + "config": { + "host": "http://localhost:5000", + "options": { path: "/best" }, + "remoteRunner": "@best/runner-headless" + } + } + ], }; diff --git a/yarn.lock b/yarn.lock index bf131c89..99d40f13 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3633,7 +3633,7 @@ chokidar@^2.0.2, chokidar@^2.0.4, chokidar@^2.1.5, chokidar@^2.1.6: optionalDependencies: fsevents "^1.2.7" -chownr@^1.0.1, chownr@^1.1.1: +chownr@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494" integrity sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g== @@ -4587,7 +4587,7 @@ dateformat@^3.0.0: resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== -debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.8, debug@^2.6.9, debug@~2.6.4, debug@~2.6.6: +debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.8, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -4608,7 +4608,7 @@ debug@^3.1.0, debug@^3.2.5, debug@^3.2.6: dependencies: ms "^2.1.1" -debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: +debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@~4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== @@ -5044,10 +5044,10 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0: dependencies: once "^1.4.0" -engine.io-client@~3.1.0: - version "3.1.6" - resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.1.6.tgz#5bdeb130f8b94a50ac5cbeb72583e7a4a063ddfd" - integrity sha512-hnuHsFluXnsKOndS4Hv6SvUrgdYx1pk2NqfaDMW+GWdgfU3+/V25Cj7I8a0x92idSpa5PIhJRKxPvp9mnoLsfg== +engine.io-client@~3.3.1: + version "3.3.2" + resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.3.2.tgz#04e068798d75beda14375a264bb3d742d7bc33aa" + integrity sha512-y0CPINnhMvPuwtqXfsGuWE8BB66+B6wTtCofQDRecMQPYX3MYUZXFNKDhdrSe3EVjgOu4V3rxdeqN/Tr91IgbQ== dependencies: component-emitter "1.2.1" component-inherit "0.0.3" @@ -5057,7 +5057,7 @@ engine.io-client@~3.1.0: indexof "0.0.1" parseqs "0.0.5" parseuri "0.0.5" - ws "~3.3.1" + ws "~6.1.0" xmlhttprequest-ssl "~1.5.4" yeast "0.1.2" @@ -5072,19 +5072,17 @@ engine.io-parser@~2.1.0, engine.io-parser@~2.1.1: blob "0.0.5" has-binary2 "~1.0.2" -engine.io@~3.1.0: - version "3.1.5" - resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-3.1.5.tgz#0e7ef9d690eb0b35597f1d4ad02a26ca2dba3845" - integrity sha512-D06ivJkYxyRrcEe0bTpNnBQNgP9d3xog+qZlLbui8EsMr/DouQpf5o9FzJnWYHEYE0YsFHllUv2R1dkgYZXHcA== +engine.io@~3.3.1: + version "3.3.2" + resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-3.3.2.tgz#18cbc8b6f36e9461c5c0f81df2b830de16058a59" + integrity sha512-AsaA9KG7cWPXWHp5FvHdDWY3AMWeZ8x+2pUVLcn71qE5AtAzgGbxuclOytygskw8XGmiQafTmnI9Bix3uihu2w== dependencies: accepts "~1.3.4" base64id "1.0.0" cookie "0.3.1" debug "~3.1.0" engine.io-parser "~2.1.0" - ws "~3.3.1" - optionalDependencies: - uws "~9.14.0" + ws "~6.1.0" enhanced-resolve@^4.1.0: version "4.1.0" @@ -5918,7 +5916,7 @@ fs-extra@^7.0.0: jsonfile "^4.0.0" universalify "^0.1.0" -fs-minipass@^1.2.3, fs-minipass@^1.2.5: +fs-minipass@^1.2.5: version "1.2.6" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.6.tgz#2c5cc30ded81282bfe8a0d7c7c1853ddeb102c07" integrity sha512-crhvyXcMejjv3Z5d2Fa9sf5xLYVCF5O1c71QxbVnbLsmYMBEvDAftewesN/HhY03YRoA7zOMxjNGrF5svGaaeQ== @@ -8653,7 +8651,7 @@ minipass@^2.2.1, minipass@^2.3.5: safe-buffer "^5.1.2" yallist "^3.0.0" -minizlib@^1.1.0, minizlib@^1.2.1: +minizlib@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.2.1.tgz#dd27ea6136243c7c880684e8672bb3a45fd9b614" integrity sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA== @@ -11404,52 +11402,53 @@ socket.io-adapter@~1.1.0: resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz#2a805e8a14d6372124dd9159ad4502f8cb07f06b" integrity sha1-KoBeihTWNyEk3ZFZrUUC+MsH8Gs= -socket.io-client@2.0.4, socket.io-client@~2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-2.0.4.tgz#0918a552406dc5e540b380dcd97afc4a64332f8e" - integrity sha1-CRilUkBtxeVAs4Dc2Xr8SmQzL44= +socket.io-client@2.2.0, socket.io-client@~2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-2.2.0.tgz#84e73ee3c43d5020ccc1a258faeeb9aec2723af7" + integrity sha512-56ZrkTDbdTLmBIyfFYesgOxsjcLnwAKoN4CiPyTVkMQj3zTUh0QAx3GbvIvLpFEOvQWu92yyWICxB0u7wkVbYA== dependencies: backo2 "1.0.2" base64-arraybuffer "0.1.5" component-bind "1.0.0" component-emitter "1.2.1" - debug "~2.6.4" - engine.io-client "~3.1.0" + debug "~3.1.0" + engine.io-client "~3.3.1" + has-binary2 "~1.0.2" has-cors "1.1.0" indexof "0.0.1" object-component "0.0.3" parseqs "0.0.5" parseuri "0.0.5" - socket.io-parser "~3.1.1" + socket.io-parser "~3.3.0" to-array "0.1.4" -socket.io-file@~2.0.2: +socket.io-file@~2.0.31: version "2.0.31" resolved "https://registry.yarnpkg.com/socket.io-file/-/socket.io-file-2.0.31.tgz#7b2a3f5e2d5dee4ce35535f9d2d2b7a0ac1afcf3" integrity sha512-88rkraeHlnxrcMb08zNjAv4pVZXXmGIcPM9V5KsxBOCVTZ9g+l2m/msYnPCU7GGvFBXM5ilzEe63d8VuKNIevA== dependencies: mime "^1.3.4" -socket.io-parser@~3.1.1: - version "3.1.3" - resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.1.3.tgz#ed2da5ee79f10955036e3da413bfd7f1e4d86c8e" - integrity sha512-g0a2HPqLguqAczs3dMECuA1RgoGFPyvDqcbaDEdCWY9g59kdUAz3YRmaJBNKXflrHNwB7Q12Gkf/0CZXfdHR7g== +socket.io-parser@~3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.3.0.tgz#2b52a96a509fdf31440ba40fed6094c7d4f1262f" + integrity sha512-hczmV6bDgdaEbVqhAeVMM/jfUfzuEZHsQg6eOmLgJht6G3mPKMxYm75w2+qhAQZ+4X+1+ATZ+QFKeOZD5riHng== dependencies: component-emitter "1.2.1" debug "~3.1.0" - has-binary2 "~1.0.2" isarray "2.0.1" -socket.io@~2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-2.0.4.tgz#c1a4590ceff87ecf13c72652f046f716b29e6014" - integrity sha1-waRZDO/4fs8TxyZS8Eb3FrKeYBQ= +socket.io@~2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-2.2.0.tgz#f0f633161ef6712c972b307598ecd08c9b1b4d5b" + integrity sha512-wxXrIuZ8AILcn+f1B4ez4hJTPG24iNgxBBDaJfT6MsyOhVYiTXWexGoPkd87ktJG8kQEcL/NBvRi64+9k4Kc0w== dependencies: - debug "~2.6.6" - engine.io "~3.1.0" + debug "~4.1.0" + engine.io "~3.3.1" + has-binary2 "~1.0.2" socket.io-adapter "~1.1.0" - socket.io-client "2.0.4" - socket.io-parser "~3.1.1" + socket.io-client "2.2.0" + socket.io-parser "~3.3.0" sockjs-client@1.3.0, sockjs-client@^1.3.0: version "1.3.0" @@ -11955,7 +11954,7 @@ tapable@^1.0.0, tapable@^1.1.0: resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== -tar@^4, tar@^4.4.8: +tar@^4, tar@^4.4.8, tar@~4.4.10: version "4.4.10" resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.10.tgz#946b2810b9a5e0b26140cf78bea6b0b0d689eba1" integrity sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA== @@ -11968,18 +11967,6 @@ tar@^4, tar@^4.4.8: safe-buffer "^5.1.2" yallist "^3.0.3" -tar@~4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.2.0.tgz#7e2bdadf55a4a04bf64a9d2680b4455e7c61d45e" - integrity sha512-8c4LjonehF+KArauze53Tbx1tfPsWiF94cS8wK8s94wSGTWHwdVMLCRxvqe0u8fzTXfnAjlpkpOAQl240K/anw== - dependencies: - chownr "^1.0.1" - fs-minipass "^1.2.3" - minipass "^2.2.1" - minizlib "^1.1.0" - mkdirp "^0.5.0" - yallist "^3.0.2" - temp-dir@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d" @@ -12362,11 +12349,6 @@ uid-number@0.0.6: resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" integrity sha1-DqEOgDXo61uOREnwbaHHMGY7qoE= -ultron@~1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c" - integrity sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og== - umask@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/umask/-/umask-1.1.0.tgz#f29cebf01df517912bb58ff9c4e50fde8e33320d" @@ -12599,11 +12581,6 @@ uuid@^2.0.1: resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a" integrity sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho= -uws@~9.14.0: - version "9.14.0" - resolved "https://registry.yarnpkg.com/uws/-/uws-9.14.0.tgz#fac8386befc33a7a3705cbd58dc47b430ca4dd95" - integrity sha512-HNMztPP5A1sKuVFmdZ6BPVpBQd5bUjNC8EFMFiICK+oho/OQsAJy5hnIx4btMHiOk8j04f/DbIlqnEZ9d72dqg== - validate-npm-package-license@^3.0.1, validate-npm-package-license@^3.0.3: version "3.0.4" resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" @@ -13025,14 +13002,12 @@ ws@^7.0.0: dependencies: async-limiter "^1.0.0" -ws@~3.3.1: - version "3.3.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2" - integrity sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA== +ws@~6.1.0: + version "6.1.4" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.4.tgz#5b5c8800afab925e94ccb29d153c8d02c1776ef9" + integrity sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA== dependencies: async-limiter "~1.0.0" - safe-buffer "~5.1.0" - ultron "~1.1.0" x-xss-protection@1.1.0: version "1.1.0"