├── index.js ├── .gitignore ├── package.json ├── src ├── worker │ ├── loopTracer.js │ └── worker.js └── main │ ├── launchWorker.js │ ├── app.js │ └── eventsReducer.js ├── .github └── FUNDING.yml ├── README.md └── yarn.lock /index.js: -------------------------------------------------------------------------------- 1 | require('./src/main/app'); 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | log.txt 2 | node_modules 3 | log-app.txt 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-event-loop-demo-server", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "engines": { 7 | "node": "11.x" 8 | }, 9 | "main": "index.js", 10 | "scripts": { 11 | "start": "node --experimental-worker index.js" 12 | }, 13 | "dependencies": { 14 | "ast-types": "^0.11.7", 15 | "babel-core": "^6.26.3", 16 | "falafel": "^2.1.0", 17 | "lodash": "^4.17.19", 18 | "node-fetch": "^2.6.1", 19 | "pretty-format": "^24.0.0", 20 | "recast": "^0.16.2", 21 | "vm2": "^3.6.10", 22 | "ws": "^6.1.3" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/worker/loopTracer.js: -------------------------------------------------------------------------------- 1 | const traceLoops = (babel) => { 2 | const t = babel.types; 3 | 4 | const transformLoop = (path) => { 5 | const iterateLoop = t.memberExpression( 6 | t.identifier('Tracer'), 7 | t.identifier('iterateLoop'), 8 | ); 9 | const callIterateLoop = t.callExpression(iterateLoop, []); 10 | path.get('body').pushContainer('body', callIterateLoop); 11 | }; 12 | 13 | return { 14 | visitor: { 15 | WhileStatement: transformLoop, 16 | DoWhileStatement: transformLoop, 17 | ForStatement: transformLoop, 18 | ForInStatement: transformLoop, 19 | ForOfStatement: transformLoop, 20 | } 21 | }; 22 | }; 23 | 24 | module.exports = { traceLoops }; 25 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [Hopding] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /src/main/launchWorker.js: -------------------------------------------------------------------------------- 1 | const { Worker } = require('worker_threads'); 2 | 3 | const WORKER_FILE = './src/worker/worker.js'; 4 | 5 | const action = (type, payload) => JSON.stringify({ type, payload }); 6 | const Messages = { 7 | UncaughtError: (error) => action('UncaughtError', { error }), 8 | Done: (exitCode) => action('Done', { exitCode }), 9 | } 10 | 11 | const launchWorker = (jsSourceCode, onEvent) => { 12 | const worker = new Worker(WORKER_FILE, { workerData: jsSourceCode }); 13 | 14 | worker.on('message', (message) => { 15 | console.log('Worker MESSAGE:', message) 16 | onEvent(message); 17 | }); 18 | 19 | worker.on('error', (error) => { 20 | console.error('Worker ERROR:', error) 21 | onEvent(Messages.UncaughtError({ 22 | name: error.name, 23 | stack: error.stack, 24 | message: error.message, 25 | })); 26 | }); 27 | 28 | worker.on('exit', (code) => { 29 | console.log('Worker EXIT:', code) 30 | onEvent(Messages.Done(code)); 31 | }); 32 | 33 | return worker; 34 | }; 35 | 36 | module.exports = { launchWorker }; 37 | -------------------------------------------------------------------------------- /src/main/app.js: -------------------------------------------------------------------------------- 1 | const WebSocket = require('ws'); 2 | const { launchWorker } = require('./launchWorker'); 3 | const { reduceEvents } = require('./eventsReducer'); 4 | 5 | // Heroku provides a PORT env var that we have to use 6 | const port = process.env.PORT || 8080; 7 | const wss = new WebSocket.Server({ port }); 8 | console.log('Running server on port:', port); 9 | 10 | const Messages = { 11 | RunCode: 'RunCode', 12 | }; 13 | 14 | wss.on('connection', (ws) => { 15 | ws.on('message', (message) => { 16 | console.log('Received:', message) 17 | const { type, payload } = JSON.parse(message); 18 | 19 | if (type === Messages.RunCode) { 20 | let events = []; 21 | let isFinished = false; 22 | 23 | const worker = launchWorker(payload, evtString => { 24 | if (!isFinished) { 25 | const evt = JSON.parse(evtString); 26 | events.push(evt); 27 | 28 | if (evt.type === 'Done') { 29 | const reducedEvents = reduceEvents(events); 30 | console.log(reducedEvents.map(JSON.stringify)) 31 | ws.send(JSON.stringify(reducedEvents)); 32 | } 33 | } 34 | }); 35 | } else { 36 | console.error('Unknown message type:', type); 37 | } 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JavaScript Visualizer 9000 Server 2 | 3 | Produces events for code submitted by https://jsv9000.app. The repo for the client is [here](https://github.com/Hopding/js-visualizer-9000-client). 4 | 5 | For example, upon receiving this input code: 6 | 7 | ```js 8 | function logA() { 9 | console.log('A'); 10 | } 11 | function logB() { 12 | console.log('B'); 13 | } 14 | function logC() { 15 | console.log('C'); 16 | } 17 | function logD() { 18 | console.log('D'); 19 | } 20 | 21 | // Click the "RUN" button to learn how this works! 22 | logA(); 23 | setTimeout(logB, 0); 24 | Promise.resolve().then(logC); 25 | logD(); 26 | 27 | // NOTE: 28 | // This is an interactive vizualization. So try 29 | // editing this code and see what happens. You 30 | // can also try playing with some of the examples 31 | // from the dropdown! 32 | ``` 33 | 34 | The server logs the following: 35 | 36 | ``` 37 | Received: {"type":"RunCode","payload":"function logA() { console.log('A') }\nfunction logB() { console.log('B') }\nfunction logC() { console.log('C') }\nfunction logD() { console.log('D') }\n\n// Click the \"RUN\" button to learn how this works!\nlogA();\nsetTimeout(logB, 0);\nPromise.resolve().then(logC);\nlogD();\n\n// NOTE:\n// This is an interactive vizualization. So try \n// editing this code and see what happens. You\n// can also try playing with some of the examples \n// from the dropdown!"} 38 | Worker MESSAGE: {"type":"EnterFunction","payload":{"id":0,"name":"logA","start":0,"end":36}} 39 | Worker MESSAGE: {"type":"ConsoleLog","payload":{"message":"A\n"}} 40 | Worker MESSAGE: {"type":"ExitFunction","payload":{"id":0,"name":"logA","start":0,"end":36}} 41 | Worker MESSAGE: {"type":"InitTimeout","payload":{"id":5,"callbackName":"logB"}} 42 | Worker MESSAGE: {"type":"InitPromise","payload":{"id":6,"parentId":2}} 43 | Worker MESSAGE: {"type":"ResolvePromise","payload":{"id":6}} 44 | Worker MESSAGE: {"type":"InitPromise","payload":{"id":7,"parentId":6}} 45 | Worker MESSAGE: {"type":"EnterFunction","payload":{"id":1,"name":"logD","start":111,"end":147}} 46 | Worker MESSAGE: {"type":"ConsoleLog","payload":{"message":"D\n"}} 47 | Worker MESSAGE: {"type":"ExitFunction","payload":{"id":1,"name":"logD","start":111,"end":147}} 48 | Worker MESSAGE: {"type":"BeforePromise","payload":{"id":7}} 49 | Worker MESSAGE: {"type":"EnterFunction","payload":{"id":2,"name":"logC","start":74,"end":110}} 50 | Worker MESSAGE: {"type":"ConsoleLog","payload":{"message":"C\n"}} 51 | Worker MESSAGE: {"type":"ExitFunction","payload":{"id":2,"name":"logC","start":74,"end":110}} 52 | Worker MESSAGE: {"type":"ResolvePromise","payload":{"id":7}} 53 | Worker MESSAGE: {"type":"AfterPromise","payload":{"id":7}} 54 | Worker MESSAGE: {"type":"BeforeTimeout","payload":{"id":5}} 55 | Worker MESSAGE: {"type":"EnterFunction","payload":{"id":3,"name":"logB","start":37,"end":73}} 56 | Worker MESSAGE: {"type":"ConsoleLog","payload":{"message":"B\n"}} 57 | Worker MESSAGE: {"type":"ExitFunction","payload":{"id":3,"name":"logB","start":37,"end":73}} 58 | Worker EXIT: 0 59 | { resolvedPromiseIds: [ 6, 7 ], 60 | promisesWithInvokedCallbacksInfo: [ { id: 7, name: 'logC' } ], 61 | parentsIdsOfPromisesWithInvokedCallbacks: [ { id: 6, name: 'logC' } ] } 62 | [ '{"type":"EnterFunction","payload":{"id":0,"name":"logA","start":0,"end":36}}', 63 | '{"type":"ConsoleLog","payload":{"message":"A\\n"}}', 64 | '{"type":"ExitFunction","payload":{"id":0,"name":"logA","start":0,"end":36}}', 65 | '{"type":"InitTimeout","payload":{"id":5,"callbackName":"logB"}}', 66 | '{"type":"InitPromise","payload":{"id":6,"parentId":2}}', 67 | '{"type":"ResolvePromise","payload":{"id":6}}', 68 | '{"type":"EnqueueMicrotask","payload":{"name":"logC"}}', 69 | '{"type":"InitPromise","payload":{"id":7,"parentId":6}}', 70 | '{"type":"EnterFunction","payload":{"id":1,"name":"logD","start":111,"end":147}}', 71 | '{"type":"ConsoleLog","payload":{"message":"D\\n"}}', 72 | '{"type":"ExitFunction","payload":{"id":1,"name":"logD","start":111,"end":147}}', 73 | '{"type":"BeforePromise","payload":{"id":7}}', 74 | '{"type":"DequeueMicrotask","payload":{}}', 75 | '{"type":"EnterFunction","payload":{"id":2,"name":"logC","start":74,"end":110}}', 76 | '{"type":"ConsoleLog","payload":{"message":"C\\n"}}', 77 | '{"type":"ExitFunction","payload":{"id":2,"name":"logC","start":74,"end":110}}', 78 | '{"type":"ResolvePromise","payload":{"id":7}}', 79 | '{"type":"AfterPromise","payload":{"id":7}}', 80 | '{"type":"Rerender","payload":{}}', 81 | '{"type":"BeforeTimeout","payload":{"id":5}}', 82 | '{"type":"EnterFunction","payload":{"id":3,"name":"logB","start":37,"end":73}}', 83 | '{"type":"ConsoleLog","payload":{"message":"B\\n"}}', 84 | '{"type":"ExitFunction","payload":{"id":3,"name":"logB","start":37,"end":73}}' ] 85 | ``` 86 | -------------------------------------------------------------------------------- /src/main/eventsReducer.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | 3 | const eventsReducer = (state, evt) => { 4 | const { type, payload } = evt; 5 | 6 | if (type === 'EarlyTermination') state.events.push(evt); 7 | if (type === 'UncaughtError') state.events.push(evt); 8 | 9 | if (type === 'ConsoleLog') state.events.push(evt); 10 | if (type === 'ConsoleWarn') state.events.push(evt); 11 | if (type === 'ConsoleError') state.events.push(evt); 12 | 13 | if (type === 'EnterFunction') { 14 | if (state.prevEvt.type === 'BeforePromise') { 15 | state.events.push({ type: 'DequeueMicrotask', payload: {} }); 16 | } 17 | if (state.prevEvt.type === 'BeforeMicrotask') { 18 | state.events.push({ type: 'DequeueMicrotask', payload: {} }); 19 | } 20 | state.events.push(evt); 21 | } 22 | if (type == 'ExitFunction') state.events.push(evt); 23 | if (type == 'ErrorFunction') state.events.push(evt); 24 | 25 | if (type === 'InitPromise') state.events.push(evt); 26 | if (type === 'ResolvePromise') { 27 | state.events.push(evt); 28 | 29 | const microtaskInfo = state.parentsIdsOfPromisesWithInvokedCallbacks 30 | .find(({ id }) => id === payload.id); 31 | 32 | if (microtaskInfo) { 33 | state.events.push({ 34 | type: 'EnqueueMicrotask', 35 | payload: { name: microtaskInfo.name } 36 | }); 37 | } 38 | } 39 | if (type === 'BeforePromise') state.events.push(evt); 40 | if (type === 'AfterPromise') state.events.push(evt); 41 | 42 | if (type === 'InitMicrotask') { 43 | state.events.push(evt); 44 | 45 | const microtaskInfo = state.parentsIdsOfMicrotasks 46 | .find(({ id }) => id === payload.id); 47 | 48 | if (microtaskInfo) { 49 | state.events.push({ 50 | type: 'EnqueueMicrotask', 51 | payload: { name: microtaskInfo.name } 52 | }); 53 | } 54 | } 55 | if (type === 'BeforeMicrotask') state.events.push(evt); 56 | if (type === 'AfterMicrotask') state.events.push(evt); 57 | 58 | if (type === 'InitTimeout') state.events.push(evt); 59 | if (type === 'BeforeTimeout') { 60 | state.events.push({ type: 'Rerender', payload: {} }); 61 | state.events.push(evt); 62 | } 63 | 64 | state.prevEvt = evt; 65 | 66 | return state; 67 | }; 68 | 69 | // TODO: Return line:column numbers for func calls 70 | 71 | const reduceEvents = (events) => { 72 | // For some reason, certain Promises (e.g. from `fetch` calls) seem to 73 | // resolve multiple times. I don't know why this happens, but it screws things 74 | // up for the view layer, so we'll just take the last one ¯\_(ツ)_/¯ 75 | events = _(events) 76 | .reverse() 77 | .uniqWith((aEvt, bEvt) => 78 | aEvt.type === 'ResolvePromise' && 79 | bEvt.type === 'ResolvePromise' && 80 | aEvt.payload.id === bEvt.payload.id 81 | ) 82 | .reverse() 83 | .value() 84 | 85 | // Before we reduce the events, we need to figure out when Microtasks 86 | // were enqueued. 87 | // 88 | // A Microtask was enqueued when its parent resolved iff the child Promise 89 | // of the parent had its callback invoked. 90 | // 91 | // A Promise has its callback invoked iff a function was entered immediately 92 | // after the Promise's `BeforePromise` event. 93 | 94 | const resolvedPromiseIds = events 95 | .filter(({ type }) => type === 'ResolvePromise') 96 | .map(({ payload: { id } }) => id); 97 | 98 | const promisesWithInvokedCallbacksInfo = events 99 | .filter(({ type }) => 100 | ['BeforePromise', 'EnterFunction', 'ExitFunction', 'ResolvePromise'].includes(type) 101 | ) 102 | .map((evt, idx, arr) => 103 | evt.type === 'BeforePromise' && (arr[idx + 1] || {}).type === 'EnterFunction' 104 | ? [evt, arr[idx + 1]] : undefined 105 | ) 106 | .filter(Boolean) 107 | .map(([beforePromiseEvt, enterFunctionEvt]) => ({ 108 | id: beforePromiseEvt.payload.id, 109 | name: enterFunctionEvt.payload.name 110 | })) 111 | 112 | const promiseChildIdToParentId = {}; 113 | events 114 | .filter(({ type }) => type === 'InitPromise') 115 | .forEach(({ payload: { id, parentId } }) => { 116 | promiseChildIdToParentId[id] = parentId; 117 | }); 118 | 119 | const parentsIdsOfPromisesWithInvokedCallbacks = promisesWithInvokedCallbacksInfo 120 | .map(({ id: childId, name }) => ({ 121 | id: promiseChildIdToParentId[childId], 122 | name, 123 | })); 124 | 125 | const microtasksWithInvokedCallbacksInfo = events 126 | .filter(({ type }) => 127 | [ 'InitMicrotask', 'BeforeMicrotask', 'AfterMicrotask', 'EnterFunction', 'ExitFunction' ].includes(type) 128 | ) 129 | .map((evt, idx, arr) => 130 | evt.type === 'BeforeMicrotask' && (arr[idx + 1] || {}).type === 'EnterFunction' 131 | ? [evt, arr[idx + 1]] : undefined 132 | ) 133 | .filter(Boolean) 134 | .map(([beforeMicrotaskEvt, enterFunctionEvt]) => ({ 135 | id: beforeMicrotaskEvt.payload.id, 136 | name: enterFunctionEvt.payload.name 137 | })); 138 | 139 | const microtaskChildIdToParentId = {}; 140 | events 141 | .filter(({ type }) => type === 'InitMicrotask') 142 | .forEach(({ payload: { id, parentId } }) => { 143 | microtaskChildIdToParentId[id] = parentId; 144 | }); 145 | 146 | const parentsIdsOfMicrotasks = microtasksWithInvokedCallbacksInfo 147 | .map(({ id: childId, name }) => ({ 148 | id: microtaskChildIdToParentId[childId], 149 | name, 150 | })); 151 | 152 | console.log({ resolvedPromiseIds, promisesWithInvokedCallbacksInfo, parentsIdsOfPromisesWithInvokedCallbacks, parentsIdsOfMicrotasks }); 153 | 154 | return events.reduce(eventsReducer, { 155 | events: [], 156 | parentsIdsOfPromisesWithInvokedCallbacks, 157 | parentsIdsOfMicrotasks, 158 | prevEvt: {}, 159 | }).events; 160 | }; 161 | 162 | module.exports = { reduceEvents }; 163 | -------------------------------------------------------------------------------- /src/worker/worker.js: -------------------------------------------------------------------------------- 1 | const { parentPort, workerData } = require('worker_threads'); 2 | const asyncHooks = require('async_hooks'); 3 | const util = require('util'); 4 | const fs = require('fs'); 5 | const babel = require('babel-core'); 6 | const { VM } = require('vm2'); 7 | 8 | const fetch = require('node-fetch'); 9 | const _ = require('lodash'); 10 | const falafel = require('falafel'); 11 | const prettyFormat = require('pretty-format'); 12 | 13 | const { traceLoops } = require('./loopTracer'); 14 | 15 | const LOG_FILE = './log.txt'; 16 | fs.writeFileSync(LOG_FILE, ''); 17 | const log = (...msg) => fs.appendFileSync( 18 | LOG_FILE, 19 | msg.map(m => _.isString(m) ? m : prettyFormat(m)).join(' ') + '\n' 20 | ); 21 | 22 | const event = (type, payload) => ({ type, payload }); 23 | const Events = { 24 | ConsoleLog: (message) => event('ConsoleLog', { message }), 25 | ConsoleWarn: (message) => event('ConsoleWarn', { message }), 26 | ConsoleError: (message) => event('ConsoleError', { message }), 27 | 28 | EnterFunction: (id, name, start, end) => event('EnterFunction', { id, name, start, end }), 29 | ExitFunction: (id, name, start, end) => event('ExitFunction', { id, name, start, end }), 30 | ErrorFunction: (message, id, name, start, end) => event('ErrorFunction', { message, id, name, start, end }), 31 | 32 | InitPromise: (id, parentId) => event('InitPromise', { id, parentId }), 33 | ResolvePromise: (id) => event('ResolvePromise', { id }), 34 | BeforePromise: (id) => event('BeforePromise', { id }), 35 | AfterPromise: (id) => event('AfterPromise', { id }), 36 | 37 | InitMicrotask: (id, parentId) => event('InitMicrotask', { id, parentId }), 38 | BeforeMicrotask: (id) => event('BeforeMicrotask', { id }), 39 | AfterMicrotask: (id) => event('AfterMicrotask', { id }), 40 | 41 | InitTimeout: (id, callbackName) => event('InitTimeout', { id, callbackName }), 42 | BeforeTimeout: (id) => event('BeforeTimeout', { id }), 43 | 44 | UncaughtError: (error) => event('UncaughtError', { 45 | name: (error || {}).name, 46 | stack: (error || {}).stack, 47 | message: (error || {}).message, 48 | }), 49 | EarlyTermination: (message) => event('EarlyTermination', { message }), 50 | }; 51 | 52 | let events = []; 53 | const postEvent = (event) => { 54 | events.push(event); 55 | parentPort.postMessage(JSON.stringify(event)); 56 | } 57 | 58 | // We only care about these async hook types: 59 | // PROMISE, Timeout 60 | const ignoredAsyncHookTypes = [ 61 | 'FSEVENTWRAP', 'FSREQCALLBACK', 'GETADDRINFOREQWRAP', 'GETNAMEINFOREQWRAP', 62 | 'HTTPPARSER', 'JSSTREAM', 'PIPECONNECTWRAP', 'PIPEWRAP', 'PROCESSWRAP', 63 | 'QUERYWRAP', 'SHUTDOWNWRAP', 'SIGNALWRAP', 'STATWATCHER', 'TCPCONNECTWRAP', 64 | 'TCPSERVERWRAP', 'TCPWRAP', 'TTYWRAP', 'UDPSENDWRAP', 'UDPWRAP', 'WRITEWRAP', 65 | 'ZLIB', 'SSLCONNECTION', 'PBKDF2REQUEST', 'RANDOMBYTESREQUEST', 'TLSWRAP', 66 | 'DNSCHANNEL', 67 | ]; 68 | const isIgnoredHookType = (type) => ignoredAsyncHookTypes.includes(type); 69 | 70 | const eid = asyncHooks.executionAsyncId(); 71 | const tid = asyncHooks.triggerAsyncId(); 72 | 73 | const asyncIdToResource = {}; 74 | 75 | const init = (asyncId, type, triggerAsyncId, resource) => { 76 | asyncIdToResource[asyncId] = resource; 77 | if (type === 'PROMISE') { 78 | postEvent(Events.InitPromise(asyncId, triggerAsyncId)); 79 | } 80 | if (type === 'Timeout') { 81 | const callbackName = resource._onTimeout.name || 'anonymous'; 82 | postEvent(Events.InitTimeout(asyncId, callbackName)); 83 | } 84 | if (type === 'Microtask') { 85 | postEvent(Events.InitMicrotask(asyncId, triggerAsyncId)); 86 | } 87 | } 88 | 89 | const before = (asyncId) => { 90 | const resource = asyncIdToResource[asyncId] || {}; 91 | const resourceName = (resource.constructor).name; 92 | if (resourceName === 'PromiseWrap') { 93 | postEvent(Events.BeforePromise(asyncId)); 94 | } 95 | if (resourceName === 'Timeout') { 96 | postEvent(Events.BeforeTimeout(asyncId)); 97 | } 98 | if (resourceName === 'AsyncResource') { 99 | postEvent(Events.BeforeMicrotask(asyncId)); 100 | } 101 | } 102 | 103 | const after = (asyncId) => { 104 | const resource = asyncIdToResource[asyncId] || {}; 105 | const resourceName = (resource.constructor).name; 106 | if (resourceName === 'PromiseWrap') { 107 | postEvent(Events.AfterPromise(asyncId)); 108 | } 109 | if (resourceName === 'AsyncResource') { 110 | postEvent(Events.AfterMicrotask(asyncId)); 111 | } 112 | } 113 | 114 | const destroy = (asyncId) => { 115 | const resource = asyncIdToResource[asyncId] || {}; 116 | } 117 | 118 | const promiseResolve = (asyncId) => { 119 | const promise = asyncIdToResource[asyncId].promise; 120 | postEvent(Events.ResolvePromise(asyncId)); 121 | } 122 | 123 | asyncHooks 124 | .createHook({ init, before, after, destroy, promiseResolve }) 125 | .enable(); 126 | 127 | const functionDefinitionTypes = [ 128 | 'FunctionDeclaration', 129 | 'FunctionExpression', 130 | 'ArrowFunctionExpression', 131 | ]; 132 | const arrowFnImplicitReturnTypesRegex = /Literal|Identifier|(\w)*Expression/; 133 | 134 | // Inspired by: http://alltom.com/pages/instrumenting-javascript/ 135 | const traceBlock = (code, fnName, start, end) => `{ 136 | const idWithExtensionToAvoidConflicts = nextId(); 137 | Tracer.enterFunc(idWithExtensionToAvoidConflicts, '${fnName}', ${start}, ${end}); 138 | try { 139 | ${code} 140 | } catch (e) { 141 | Tracer.errorFunc(e.message, idWithExtensionToAvoidConflicts, '${fnName}', ${start}, ${end}); 142 | throw e; 143 | } finally { 144 | Tracer.exitFunc(idWithExtensionToAvoidConflicts, '${fnName}', ${start}, ${end}); 145 | } 146 | }` 147 | 148 | const jsSourceCode = workerData; 149 | 150 | // TODO: Convert all this to babel transform(s) 151 | // TODO: HANDLE GENERATORS/ASYNC-AWAIT 152 | const output = falafel(jsSourceCode, (node) => { 153 | 154 | const parentType = node.parent && node.parent.type; 155 | const isBlockStatement = node.type === 'BlockStatement'; 156 | const isFunctionBody = functionDefinitionTypes.includes(parentType); 157 | const isArrowFnReturnType = arrowFnImplicitReturnTypesRegex.test(node.type); 158 | const isArrowFunctionBody = parentType === 'ArrowFunctionExpression'; 159 | const isArrowFn = node.type === 'ArrowFunctionExpression'; 160 | 161 | if (isBlockStatement && isFunctionBody) { 162 | const { start, end } = node.parent; 163 | const fnName = (node.parent.id && node.parent.id.name) || 'anonymous'; 164 | const block = node.source(); 165 | const blockWithoutCurlies = block.substring(1, block.length - 1); 166 | node.update(traceBlock(blockWithoutCurlies, fnName, start, end)) 167 | } 168 | else if (isArrowFnReturnType && isArrowFunctionBody) { 169 | const { start, end, params } = node.parent; 170 | 171 | const isParamIdentifier = params.some(param => param === node); 172 | 173 | if (!isParamIdentifier) { 174 | const fnName = (node.parent.id && node.parent.id.name) || 'anonymous'; 175 | const block = node.source(); 176 | const returnedBlock = `return (${block});`; 177 | node.update(traceBlock(returnedBlock, fnName, start, end)) 178 | } 179 | } 180 | else if (isArrowFn) { 181 | const body = node.source(); 182 | const firstCurly = body.indexOf('{'); 183 | const lastCurly = body.lastIndexOf('}'); 184 | const bodyHasCurlies = firstCurly !== -1 && lastCurly !== -1; 185 | 186 | // We already updated all arrow function bodies to have curlies, so here 187 | // we can assume if a body looks like `({ ... })`, then we need to remove 188 | // the parenthesis. 189 | if (bodyHasCurlies) { 190 | const parensNeedStripped = body[firstCurly - 1] === '('; 191 | if (parensNeedStripped) { 192 | const bodyBlock = body.substring(firstCurly, lastCurly + 1); 193 | const bodyWithoutParens = `() => ${bodyBlock}`; 194 | node.update(bodyWithoutParens); 195 | } 196 | } 197 | } 198 | 199 | }); 200 | 201 | const modifiedSource = babel 202 | .transform(output.toString(), { plugins: [traceLoops] }) 203 | .code; 204 | 205 | // TODO: Maybe change this name to avoid conflicts? 206 | const nextId = (() => { 207 | let id = 0; 208 | return () => id++; 209 | })(); 210 | 211 | const arrToPrettyStr = (arr) => 212 | arr.map(a => _.isString(a) ? a : prettyFormat(a)).join(' ') + '\n' 213 | 214 | const START_TIME = Date.now(); 215 | const TIMEOUT_MILLIS = 5000; 216 | const EVENT_LIMIT = 500; 217 | 218 | const Tracer = { 219 | enterFunc: (id, name, start, end) => postEvent(Events.EnterFunction(id, name, start, end)), 220 | exitFunc: (id, name, start, end) => postEvent(Events.ExitFunction(id, name, start, end)), 221 | errorFunc: (message, id, name, start, end) => postEvent(Events.ErrorFunction(message, id, name, start, end)), 222 | log: (...args) => postEvent(Events.ConsoleLog(arrToPrettyStr(args))), 223 | warn: (...args) => postEvent(Events.ConsoleWarn(arrToPrettyStr(args))), 224 | error: (...args) => postEvent(Events.ConsoleError(arrToPrettyStr(args))), 225 | iterateLoop: () => { 226 | const hasTimedOut = (Date.now() - START_TIME) > TIMEOUT_MILLIS; 227 | const reachedEventLimit = events.length >= EVENT_LIMIT; 228 | const shouldTerminate = reachedEventLimit || hasTimedOut; 229 | if (shouldTerminate) { 230 | postEvent(Events.EarlyTermination(hasTimedOut 231 | ? `Terminated early: Timeout of ${TIMEOUT_MILLIS} millis exceeded.` 232 | : `Termianted early: Event limit of ${EVENT_LIMIT} exceeded.` 233 | )); 234 | process.exit(1); 235 | } 236 | }, 237 | }; 238 | 239 | // E.g. call stack size exceeded errors... 240 | process.on('uncaughtException', (err) => { 241 | postEvent(Events.UncaughtError(err)); 242 | process.exit(1); 243 | }); 244 | 245 | const vm = new VM({ 246 | timeout: 6000, 247 | sandbox: { 248 | nextId, 249 | Tracer, 250 | fetch, 251 | _, 252 | lodash: _, 253 | setTimeout, 254 | queueMicrotask, 255 | console: { 256 | log: Tracer.log, 257 | warn: Tracer.warn, 258 | error: Tracer.error, 259 | }, 260 | }, 261 | }); 262 | 263 | vm.run(modifiedSource); 264 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | acorn@^5.0.0: 6 | version "5.7.4" 7 | resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e" 8 | integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg== 9 | 10 | ansi-regex@^2.0.0: 11 | version "2.1.1" 12 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" 13 | integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= 14 | 15 | ansi-regex@^4.0.0: 16 | version "4.0.0" 17 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.0.0.tgz#70de791edf021404c3fd615aa89118ae0432e5a9" 18 | integrity sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w== 19 | 20 | ansi-styles@^2.2.1: 21 | version "2.2.1" 22 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" 23 | integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= 24 | 25 | ansi-styles@^3.2.0: 26 | version "3.2.1" 27 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" 28 | integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== 29 | dependencies: 30 | color-convert "^1.9.0" 31 | 32 | ast-types@0.11.7, ast-types@^0.11.7: 33 | version "0.11.7" 34 | resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.11.7.tgz#f318bf44e339db6a320be0009ded64ec1471f46c" 35 | integrity sha512-2mP3TwtkY/aTv5X3ZsMpNAbOnyoC/aMJwJSoaELPkHId0nSQgFcnU4dRW3isxiz7+zBexk0ym3WNVjMiQBnJSw== 36 | 37 | async-limiter@~1.0.0: 38 | version "1.0.0" 39 | resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" 40 | integrity sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg== 41 | 42 | babel-code-frame@^6.26.0: 43 | version "6.26.0" 44 | resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" 45 | integrity sha1-Y/1D99weO7fONZR9uP42mj9Yx0s= 46 | dependencies: 47 | chalk "^1.1.3" 48 | esutils "^2.0.2" 49 | js-tokens "^3.0.2" 50 | 51 | babel-core@^6.26.0, babel-core@^6.26.3: 52 | version "6.26.3" 53 | resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.3.tgz#b2e2f09e342d0f0c88e2f02e067794125e75c207" 54 | integrity sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA== 55 | dependencies: 56 | babel-code-frame "^6.26.0" 57 | babel-generator "^6.26.0" 58 | babel-helpers "^6.24.1" 59 | babel-messages "^6.23.0" 60 | babel-register "^6.26.0" 61 | babel-runtime "^6.26.0" 62 | babel-template "^6.26.0" 63 | babel-traverse "^6.26.0" 64 | babel-types "^6.26.0" 65 | babylon "^6.18.0" 66 | convert-source-map "^1.5.1" 67 | debug "^2.6.9" 68 | json5 "^0.5.1" 69 | lodash "^4.17.4" 70 | minimatch "^3.0.4" 71 | path-is-absolute "^1.0.1" 72 | private "^0.1.8" 73 | slash "^1.0.0" 74 | source-map "^0.5.7" 75 | 76 | babel-generator@^6.26.0: 77 | version "6.26.1" 78 | resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90" 79 | integrity sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA== 80 | dependencies: 81 | babel-messages "^6.23.0" 82 | babel-runtime "^6.26.0" 83 | babel-types "^6.26.0" 84 | detect-indent "^4.0.0" 85 | jsesc "^1.3.0" 86 | lodash "^4.17.4" 87 | source-map "^0.5.7" 88 | trim-right "^1.0.1" 89 | 90 | babel-helpers@^6.24.1: 91 | version "6.24.1" 92 | resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2" 93 | integrity sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI= 94 | dependencies: 95 | babel-runtime "^6.22.0" 96 | babel-template "^6.24.1" 97 | 98 | babel-messages@^6.23.0: 99 | version "6.23.0" 100 | resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" 101 | integrity sha1-8830cDhYA1sqKVHG7F7fbGLyYw4= 102 | dependencies: 103 | babel-runtime "^6.22.0" 104 | 105 | babel-register@^6.26.0: 106 | version "6.26.0" 107 | resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071" 108 | integrity sha1-btAhFz4vy0htestFxgCahW9kcHE= 109 | dependencies: 110 | babel-core "^6.26.0" 111 | babel-runtime "^6.26.0" 112 | core-js "^2.5.0" 113 | home-or-tmp "^2.0.0" 114 | lodash "^4.17.4" 115 | mkdirp "^0.5.1" 116 | source-map-support "^0.4.15" 117 | 118 | babel-runtime@^6.22.0, babel-runtime@^6.26.0: 119 | version "6.26.0" 120 | resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" 121 | integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4= 122 | dependencies: 123 | core-js "^2.4.0" 124 | regenerator-runtime "^0.11.0" 125 | 126 | babel-template@^6.24.1, babel-template@^6.26.0: 127 | version "6.26.0" 128 | resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02" 129 | integrity sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI= 130 | dependencies: 131 | babel-runtime "^6.26.0" 132 | babel-traverse "^6.26.0" 133 | babel-types "^6.26.0" 134 | babylon "^6.18.0" 135 | lodash "^4.17.4" 136 | 137 | babel-traverse@^6.26.0: 138 | version "6.26.0" 139 | resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee" 140 | integrity sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4= 141 | dependencies: 142 | babel-code-frame "^6.26.0" 143 | babel-messages "^6.23.0" 144 | babel-runtime "^6.26.0" 145 | babel-types "^6.26.0" 146 | babylon "^6.18.0" 147 | debug "^2.6.8" 148 | globals "^9.18.0" 149 | invariant "^2.2.2" 150 | lodash "^4.17.4" 151 | 152 | babel-types@^6.26.0: 153 | version "6.26.0" 154 | resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" 155 | integrity sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc= 156 | dependencies: 157 | babel-runtime "^6.26.0" 158 | esutils "^2.0.2" 159 | lodash "^4.17.4" 160 | to-fast-properties "^1.0.3" 161 | 162 | babylon@^6.18.0: 163 | version "6.18.0" 164 | resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" 165 | integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ== 166 | 167 | balanced-match@^1.0.0: 168 | version "1.0.0" 169 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 170 | integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= 171 | 172 | brace-expansion@^1.1.7: 173 | version "1.1.11" 174 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 175 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 176 | dependencies: 177 | balanced-match "^1.0.0" 178 | concat-map "0.0.1" 179 | 180 | chalk@^1.1.3: 181 | version "1.1.3" 182 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" 183 | integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= 184 | dependencies: 185 | ansi-styles "^2.2.1" 186 | escape-string-regexp "^1.0.2" 187 | has-ansi "^2.0.0" 188 | strip-ansi "^3.0.0" 189 | supports-color "^2.0.0" 190 | 191 | color-convert@^1.9.0: 192 | version "1.9.3" 193 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" 194 | integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== 195 | dependencies: 196 | color-name "1.1.3" 197 | 198 | color-name@1.1.3: 199 | version "1.1.3" 200 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" 201 | integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= 202 | 203 | concat-map@0.0.1: 204 | version "0.0.1" 205 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 206 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 207 | 208 | convert-source-map@^1.5.1: 209 | version "1.6.0" 210 | resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20" 211 | integrity sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A== 212 | dependencies: 213 | safe-buffer "~5.1.1" 214 | 215 | core-js@^2.4.0, core-js@^2.5.0: 216 | version "2.6.3" 217 | resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.3.tgz#4b70938bdffdaf64931e66e2db158f0892289c49" 218 | integrity sha512-l00tmFFZOBHtYhN4Cz7k32VM7vTn3rE2ANjQDxdEN6zmXZ/xq1jQuutnmHvMG1ZJ7xd72+TA5YpUK8wz3rWsfQ== 219 | 220 | debug@^2.6.8, debug@^2.6.9: 221 | version "2.6.9" 222 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" 223 | integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== 224 | dependencies: 225 | ms "2.0.0" 226 | 227 | detect-indent@^4.0.0: 228 | version "4.0.0" 229 | resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" 230 | integrity sha1-920GQ1LN9Docts5hnE7jqUdd4gg= 231 | dependencies: 232 | repeating "^2.0.0" 233 | 234 | escape-string-regexp@^1.0.2: 235 | version "1.0.5" 236 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 237 | integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= 238 | 239 | esprima@~4.0.0: 240 | version "4.0.1" 241 | resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" 242 | integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== 243 | 244 | esutils@^2.0.2: 245 | version "2.0.2" 246 | resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" 247 | integrity sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs= 248 | 249 | falafel@^2.1.0: 250 | version "2.1.0" 251 | resolved "https://registry.yarnpkg.com/falafel/-/falafel-2.1.0.tgz#96bb17761daba94f46d001738b3cedf3a67fe06c" 252 | integrity sha1-lrsXdh2rqU9G0AFzizzt86Z/4Gw= 253 | dependencies: 254 | acorn "^5.0.0" 255 | foreach "^2.0.5" 256 | isarray "0.0.1" 257 | object-keys "^1.0.6" 258 | 259 | foreach@^2.0.5: 260 | version "2.0.5" 261 | resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" 262 | integrity sha1-C+4AUBiusmDQo6865ljdATbsG5k= 263 | 264 | globals@^9.18.0: 265 | version "9.18.0" 266 | resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" 267 | integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ== 268 | 269 | has-ansi@^2.0.0: 270 | version "2.0.0" 271 | resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" 272 | integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= 273 | dependencies: 274 | ansi-regex "^2.0.0" 275 | 276 | home-or-tmp@^2.0.0: 277 | version "2.0.0" 278 | resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8" 279 | integrity sha1-42w/LSyufXRqhX440Y1fMqeILbg= 280 | dependencies: 281 | os-homedir "^1.0.0" 282 | os-tmpdir "^1.0.1" 283 | 284 | invariant@^2.2.2: 285 | version "2.2.4" 286 | resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" 287 | integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== 288 | dependencies: 289 | loose-envify "^1.0.0" 290 | 291 | is-finite@^1.0.0: 292 | version "1.0.2" 293 | resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" 294 | integrity sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko= 295 | dependencies: 296 | number-is-nan "^1.0.0" 297 | 298 | isarray@0.0.1: 299 | version "0.0.1" 300 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" 301 | integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= 302 | 303 | "js-tokens@^3.0.0 || ^4.0.0": 304 | version "4.0.0" 305 | resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" 306 | integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== 307 | 308 | js-tokens@^3.0.2: 309 | version "3.0.2" 310 | resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" 311 | integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls= 312 | 313 | jsesc@^1.3.0: 314 | version "1.3.0" 315 | resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" 316 | integrity sha1-RsP+yMGJKxKwgz25vHYiF226s0s= 317 | 318 | json5@^0.5.1: 319 | version "0.5.1" 320 | resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" 321 | integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE= 322 | 323 | lodash@^4.17.19, lodash@^4.17.4: 324 | version "4.17.19" 325 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" 326 | integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== 327 | 328 | loose-envify@^1.0.0: 329 | version "1.4.0" 330 | resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" 331 | integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== 332 | dependencies: 333 | js-tokens "^3.0.0 || ^4.0.0" 334 | 335 | minimatch@^3.0.4: 336 | version "3.0.4" 337 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 338 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== 339 | dependencies: 340 | brace-expansion "^1.1.7" 341 | 342 | minimist@0.0.8: 343 | version "0.0.8" 344 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" 345 | integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= 346 | 347 | mkdirp@^0.5.1: 348 | version "0.5.1" 349 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" 350 | integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= 351 | dependencies: 352 | minimist "0.0.8" 353 | 354 | ms@2.0.0: 355 | version "2.0.0" 356 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 357 | integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= 358 | 359 | node-fetch@^2.6.1: 360 | version "2.6.1" 361 | resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" 362 | integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== 363 | 364 | number-is-nan@^1.0.0: 365 | version "1.0.1" 366 | resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" 367 | integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= 368 | 369 | object-keys@^1.0.6: 370 | version "1.0.12" 371 | resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.12.tgz#09c53855377575310cca62f55bb334abff7b3ed2" 372 | integrity sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag== 373 | 374 | os-homedir@^1.0.0: 375 | version "1.0.2" 376 | resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" 377 | integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= 378 | 379 | os-tmpdir@^1.0.1: 380 | version "1.0.2" 381 | resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" 382 | integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= 383 | 384 | path-is-absolute@^1.0.1: 385 | version "1.0.1" 386 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 387 | integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= 388 | 389 | pretty-format@^24.0.0: 390 | version "24.0.0" 391 | resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.0.0.tgz#cb6599fd73ac088e37ed682f61291e4678f48591" 392 | integrity sha512-LszZaKG665djUcqg5ZQq+XzezHLKrxsA86ZABTozp+oNhkdqa+tG2dX4qa6ERl5c/sRDrAa3lHmwnvKoP+OG/g== 393 | dependencies: 394 | ansi-regex "^4.0.0" 395 | ansi-styles "^3.2.0" 396 | 397 | private@^0.1.8, private@~0.1.5: 398 | version "0.1.8" 399 | resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" 400 | integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== 401 | 402 | recast@^0.16.2: 403 | version "0.16.2" 404 | resolved "https://registry.yarnpkg.com/recast/-/recast-0.16.2.tgz#3796ebad5fe49ed85473b479cd6df554ad725dc2" 405 | integrity sha512-O/7qXi51DPjRVdbrpNzoBQH5dnAPQNbfoOFyRiUwreTMJfIHYOEBzwuH+c0+/BTSJ3CQyKs6ILSWXhESH6Op3A== 406 | dependencies: 407 | ast-types "0.11.7" 408 | esprima "~4.0.0" 409 | private "~0.1.5" 410 | source-map "~0.6.1" 411 | 412 | regenerator-runtime@^0.11.0: 413 | version "0.11.1" 414 | resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" 415 | integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== 416 | 417 | repeating@^2.0.0: 418 | version "2.0.1" 419 | resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" 420 | integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo= 421 | dependencies: 422 | is-finite "^1.0.0" 423 | 424 | safe-buffer@~5.1.1: 425 | version "5.1.2" 426 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" 427 | integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== 428 | 429 | slash@^1.0.0: 430 | version "1.0.0" 431 | resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" 432 | integrity sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU= 433 | 434 | source-map-support@^0.4.15: 435 | version "0.4.18" 436 | resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" 437 | integrity sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA== 438 | dependencies: 439 | source-map "^0.5.6" 440 | 441 | source-map@^0.5.6, source-map@^0.5.7: 442 | version "0.5.7" 443 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" 444 | integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= 445 | 446 | source-map@~0.6.1: 447 | version "0.6.1" 448 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" 449 | integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== 450 | 451 | strip-ansi@^3.0.0: 452 | version "3.0.1" 453 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" 454 | integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= 455 | dependencies: 456 | ansi-regex "^2.0.0" 457 | 458 | supports-color@^2.0.0: 459 | version "2.0.0" 460 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" 461 | integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= 462 | 463 | to-fast-properties@^1.0.3: 464 | version "1.0.3" 465 | resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" 466 | integrity sha1-uDVx+k2MJbguIxsG46MFXeTKGkc= 467 | 468 | trim-right@^1.0.1: 469 | version "1.0.1" 470 | resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" 471 | integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= 472 | 473 | vm2@^3.6.10: 474 | version "3.6.10" 475 | resolved "https://registry.yarnpkg.com/vm2/-/vm2-3.6.10.tgz#a9066c3849ad8a3ea35b9c0cf79a6589ce6e3cbe" 476 | integrity sha512-p4LBl7theIhmKaWPdCn25kEIG0bfDDEDx1lexXH7gcCu9pHIT+PKFgofwLHVHUGhe39lKExeaYVEZtdbQhdl2g== 477 | 478 | ws@^6.1.3: 479 | version "6.1.3" 480 | resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.3.tgz#d2d2e5f0e3c700ef2de89080ebc0ac6e1bf3a72d" 481 | integrity sha512-tbSxiT+qJI223AP4iLfQbkbxkwdFcneYinM2+x46Gx2wgvbaOMO36czfdfVUBRTHvzAMRhDd98sA5d/BuWbQdg== 482 | dependencies: 483 | async-limiter "~1.0.0" 484 | --------------------------------------------------------------------------------