├── usecases ├── compress │ ├── fixtures │ │ ├── audio.mp3 │ │ ├── somepath │ │ ├── video.mkv │ │ └── directory │ │ │ ├── bar │ │ │ └── foo │ ├── package.json │ ├── task.js │ └── worker.js ├── gulp │ ├── README.md │ ├── task.js │ ├── gulpfile.js │ ├── package.json │ └── worker.js ├── cksfv │ ├── README.md │ ├── task.js │ ├── package.json │ ├── readLine.js │ ├── noWorker.js │ └── worker.js └── socket │ ├── worker.js │ └── task.js ├── packages ├── relieve-logger │ ├── test │ │ ├── fixtures │ │ │ └── .gitkeep │ │ └── index.js │ ├── rotate.js │ ├── package.json │ ├── README.md │ ├── index.js │ └── package-lock.json ├── relieve │ ├── IPCEE.js │ ├── test │ │ ├── mocha.opts │ │ ├── fixtures │ │ │ ├── answer.js │ │ │ ├── timeout.js │ │ │ ├── arguments.js │ │ │ ├── server.js │ │ │ └── script.js │ │ ├── tools │ │ │ └── global.js │ │ ├── index.js │ │ ├── utils │ │ │ └── defineNameProperty.js │ │ ├── tasks │ │ │ ├── ForkTask.js │ │ │ ├── CallableTask.js │ │ │ └── ScriptTask.js │ │ ├── strategies │ │ │ └── WeightedStrategy.js │ │ └── workers │ │ │ ├── Worker.js │ │ │ ├── QueueWorker.js │ │ │ └── CloudWorker.js │ ├── index.js │ ├── tasks │ │ ├── index.js │ │ ├── ForkTask.js │ │ ├── CallableTask.js │ │ └── ScriptTask.js │ ├── workers │ │ ├── index.js │ │ ├── QueueWorker.js │ │ ├── CloudWorker.js │ │ └── Worker.js │ ├── containers │ │ ├── IPCContainer.js │ │ ├── ArgumentsContainer.js │ │ ├── MonitorContainer.js │ │ ├── ScriptContainer.js │ │ └── CallableContainer.js │ ├── utils │ │ ├── readOnly.js │ │ ├── listenersPropagation.js │ │ └── defineNameProperty.js │ ├── package.json │ ├── strategies │ │ └── WeightedStrategy.js │ └── package-lock.json └── relieve-failsafe │ ├── test │ ├── index.js │ ├── group.js │ ├── server.js │ └── end2end.js │ ├── examples │ ├── task.js │ └── master.js │ ├── src │ ├── constants.js~ │ ├── constants.js │ ├── master.js │ ├── container.js │ ├── tcpee │ │ ├── group.js │ │ └── server.js │ └── index.js │ ├── package.json │ ├── README.md │ └── package-lock.json ├── .gitignore ├── .npmignore ├── examples ├── images │ └── relieve.jpg ├── 2-ScriptTask.md ├── 1-ForkTask.md ├── 4-Worker.md ├── 3-CallableTask.md ├── 7-Containers.md ├── 8-Interfaces.md ├── 6-CloudWorker.md └── 5-QueueWorker.md ├── lerna.json ├── .eslintrc ├── appveyor.yml ├── .travis.yml ├── package.json └── README.md /usecases/compress/fixtures/audio.mp3: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /usecases/compress/fixtures/somepath: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /usecases/compress/fixtures/video.mkv: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /usecases/compress/fixtures/directory/bar: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /usecases/compress/fixtures/directory/foo: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/relieve-logger/test/fixtures/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/relieve/IPCEE.js: -------------------------------------------------------------------------------- 1 | module.exports = require('ipcee') 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage 2 | docs 3 | node_modules 4 | relieve.count 5 | *.log 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | usecases 2 | examples 3 | docs 4 | coverage 5 | node_modules 6 | -------------------------------------------------------------------------------- /packages/relieve/test/mocha.opts: -------------------------------------------------------------------------------- 1 | --reporter spec 2 | --require test/tools/global.js 3 | --exit 4 | -------------------------------------------------------------------------------- /examples/images/relieve.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soyuka/relieve/HEAD/examples/images/relieve.jpg -------------------------------------------------------------------------------- /packages/relieve/test/fixtures/answer.js: -------------------------------------------------------------------------------- 1 | process.on('message', function(d) { 2 | process.send(d) 3 | }) 4 | -------------------------------------------------------------------------------- /packages/relieve-failsafe/test/index.js: -------------------------------------------------------------------------------- 1 | require('./group.js') 2 | require('./server.js') 3 | require('./end2end.js') 4 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "lerna": "2.0.0-beta.38", 3 | "packages": [ 4 | "packages/*" 5 | ], 6 | "version": "2.2.5" 7 | } 8 | -------------------------------------------------------------------------------- /packages/relieve/test/fixtures/timeout.js: -------------------------------------------------------------------------------- 1 | const ipc = process.relieve.ipc 2 | 3 | setTimeout(function() { 4 | ipc.send('response', 'ok') 5 | process.exit(0) 6 | }, 200) 7 | -------------------------------------------------------------------------------- /packages/relieve/test/fixtures/arguments.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | start: function() { 4 | process.relieve.ipc.send('arguments', process.relieve.argv) 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /usecases/gulp/README.md: -------------------------------------------------------------------------------- 1 | # Gulp relieve 2 | 3 | Execute gulp tasks in separated processes: 4 | 5 | ``` 6 | # this works on the cwd gulpfile 7 | node worker.js foo bar 8 | ``` 9 | -------------------------------------------------------------------------------- /packages/relieve/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | tasks: require('./tasks'), 3 | workers: require('./workers'), 4 | IPCEE: require('ipcee'), 5 | ipcee: require('ipcee') 6 | } 7 | -------------------------------------------------------------------------------- /usecases/cksfv/README.md: -------------------------------------------------------------------------------- 1 | # Cksfv relieve 2 | 3 | Execute a CRC check on each sfv listed file. 4 | 5 | ``` 6 | node worker.js checkthis.sfv 7 | node noWorker.js checkthis.sfv 8 | ``` 9 | -------------------------------------------------------------------------------- /packages/relieve/test/tools/global.js: -------------------------------------------------------------------------------- 1 | expect = require('chai').expect 2 | src = require('path').resolve(__dirname, '../..') 3 | fixtures = require('path').resolve(__dirname, '../fixtures') 4 | -------------------------------------------------------------------------------- /usecases/gulp/task.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp') 2 | require(require('path').resolve(process.cwd(), 'gulpfile.js')) 3 | gulp.start(process.argv[3], function() { 4 | process.exit(0) 5 | }) 6 | -------------------------------------------------------------------------------- /packages/relieve/tasks/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | CallableTask: require('./CallableTask.js'), 3 | ForkTask: require('./ForkTask.js'), 4 | ScriptTask: require('./ScriptTask.js') 5 | } 6 | -------------------------------------------------------------------------------- /packages/relieve/workers/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | CloudWorker: require('./CloudWorker.js'), 3 | QueueWorker: require('./QueueWorker.js'), 4 | Worker: require('./Worker.js') 5 | } 6 | -------------------------------------------------------------------------------- /packages/relieve/containers/IPCContainer.js: -------------------------------------------------------------------------------- 1 | const IPCEE = require('ipcee') 2 | 3 | require('./ArgumentsContainer.js') 4 | 5 | process.relieve.ipc = IPCEE(process, process.relieve.containerArgs.eventemitter) 6 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true 4 | }, 5 | "ecmaFeatures": { 6 | "arrowFunctions": true, 7 | "templateStrings": true, 8 | "blockBindings": true, 9 | "forOf": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /usecases/gulp/gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp') 2 | 3 | gulp.task('foo', function() { 4 | console.log('hello'); 5 | return Promise.resolve() 6 | }) 7 | 8 | gulp.task('bar', function() { 9 | console.log('world'); 10 | return Promise.resolve() 11 | }) 12 | 13 | gulp.task('default', ['foo', 'bar']) 14 | -------------------------------------------------------------------------------- /packages/relieve-failsafe/examples/task.js: -------------------------------------------------------------------------------- 1 | // task.js 2 | const ipc = process.relieve.ipc 3 | 4 | module.exports = { 5 | start: function() { 6 | ipc.on('ping', function() { 7 | ipc.send('pong') 8 | }) 9 | 10 | setInterval(e => { 11 | console.log('Still alive') 12 | }, 1000) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /usecases/cksfv/task.js: -------------------------------------------------------------------------------- 1 | var channel 2 | var readLine = require('./readLine.js') 3 | 4 | module.exports = { 5 | setChannel: function(c) { 6 | channel = c 7 | }, 8 | readLine: function(path, line) { 9 | readLine(path, line) 10 | .then(function(resp) { 11 | channel.send('cksfv', resp) 12 | process.exit(0) 13 | }) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/relieve/containers/ArgumentsContainer.js: -------------------------------------------------------------------------------- 1 | let processArgv = process.argv.map(function(e, i) { 2 | if(i < 3) 3 | return e 4 | 5 | return JSON.parse(e) 6 | }) 7 | 8 | if (!process.relieve) { 9 | process.relieve = {} 10 | } 11 | 12 | process.relieve.argv = [].slice.call(processArgv, 2) 13 | process.relieve.containerArgs = process.relieve.argv.pop() 14 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | init: 2 | - git config --global core.autocrlf input 3 | 4 | environment: 5 | nodejs_version: "6" 6 | 7 | install: 8 | - ps: Install-Product node $env:nodejs_version 9 | - npm install lerna -g 10 | - lerna bootstrap 11 | - lerna run test 12 | test_script: 13 | - node --version 14 | - npm --version 15 | - cmd: npm test 16 | 17 | build: off 18 | -------------------------------------------------------------------------------- /packages/relieve/test/fixtures/server.js: -------------------------------------------------------------------------------- 1 | const ipc = process.relieve.ipc 2 | 3 | ipc.send('started') 4 | 5 | ipc.on('ping', function() { 6 | ipc.send('pong') 7 | }) 8 | 9 | ipc.on('ping.me', function() { 10 | ipc.send('me.pong') 11 | }) 12 | 13 | module.exports = { 14 | hello: function() { 15 | return 'world'; 16 | }, 17 | me: function() { 18 | ipc.send('calling') 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/relieve-failsafe/examples/master.js: -------------------------------------------------------------------------------- 1 | // master.js 2 | const ScriptTask = require('relieve/tasks/ScriptTask') 3 | const FailSafe = require('../src/index.js') 4 | 5 | const task = new ScriptTask(`${__dirname}/task.js`, { 6 | interfaces: [new FailSafe()] 7 | }) 8 | 9 | task.start() 10 | .then(() => { 11 | task.on('pong', () => { 12 | console.log('got pong!') 13 | }) 14 | 15 | task.send('ping') 16 | }) 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | branches: 3 | only: 4 | - master 5 | node_js: 6 | - "8" 7 | - "10" 8 | matrix: 9 | fast_finish: true 10 | before_script: 11 | - npm i -g istanbul codeclimate-test-reporter mocha lerna 12 | script: 13 | - lerna bootstrap 14 | - lerna run test 15 | sudo: false 16 | after_script: 17 | - istanbul cover _mocha -- --exit 18 | - codeclimate-test-reporter < ./coverage/lcov.info 19 | -------------------------------------------------------------------------------- /usecases/compress/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "compress", 3 | "version": "1.0.0", 4 | "description": "Make zip in separated processes", 5 | "main": "worker.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "Antoine Bluchet ", 11 | "license": "MIT", 12 | "dependencies": { 13 | "archiver": "^0.16.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /usecases/cksfv/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cksfv-relieve", 3 | "version": "1.0.0", 4 | "description": "CRC check on an sfv file", 5 | "main": "worker.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "Antoine Bluchet ", 11 | "license": "MIT", 12 | "dependencies": { 13 | "bluebird": "^3.0.5", 14 | "crc": "^3.3.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /usecases/gulp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gulp-relieve", 3 | "version": "1.0.0", 4 | "description": "Execute gulp tasks in separated processes", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "Antoine Bluchet ", 11 | "license": "MIT", 12 | "dependencies": { 13 | "gulp": "^3.9.0", 14 | "minimist": "^1.2.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/relieve/utils/readOnly.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * registers a readOnly property 5 | * @param {Function} Fn 6 | * @param {String} key 7 | * @param {Function} getter 8 | */ 9 | function readOnly(Fn, key, getter) { 10 | Object.defineProperty(Fn.prototype, key, { 11 | get: getter, 12 | set: function setName(name) { 13 | throw new ReferenceError('Property is read-only') 14 | } 15 | }) 16 | } 17 | 18 | module.exports = readOnly 19 | -------------------------------------------------------------------------------- /packages/relieve-failsafe/src/constants.js~: -------------------------------------------------------------------------------- 1 | const tmpdir = require('os').tmpdir() 2 | const extend = require('util')._extend 3 | const constants = { 4 | SOCKET: `${tmpdir}/relieve.sock`, 5 | //this keeps a number of alive sockets in case of failure wait until they come back or timeout 6 | PERSISTENCE: `${tmpdir}/relieve.count`, 7 | TIMEOUT: 5000 //timeout to wait before resolving the tcpee group 8 | } 9 | 10 | module.exports = function(options) { 11 | return extend(constants, options) 12 | } 13 | -------------------------------------------------------------------------------- /packages/relieve-logger/rotate.js: -------------------------------------------------------------------------------- 1 | const Promise = require('bluebird') 2 | const fs = Promise.promisifyAll(require('fs')) 3 | const p = require('path') 4 | const exists = require('@soyuka/exists') 5 | 6 | function rotate(path, i = 0) { 7 | let ext = p.extname(path) 8 | let basename = p.basename(path) 9 | 10 | let newPath = p.join(p.dirname(path), basename + i + ext) 11 | 12 | return exists(newPath) 13 | .then((exists) => exists ? rotate(path, ++i) : fs.renameAsync(path, newPath)) 14 | } 15 | 16 | module.exports = rotate 17 | -------------------------------------------------------------------------------- /packages/relieve/test/fixtures/script.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | interval: null, 3 | getMe: function(data) { 4 | return Promise.resolve(data) 5 | }, 6 | callMe: function(data) { 7 | var self = this 8 | this.interval = setInterval(function() { 9 | process.relieve.ipc.send('working') 10 | }) 11 | }, 12 | stopMe: function() { 13 | clearInterval(this.interval) 14 | return Promise.resolve(true) 15 | }, 16 | throw: function() { 17 | throw new Error('Fail') 18 | }, 19 | name: 'script' 20 | } 21 | -------------------------------------------------------------------------------- /packages/relieve/test/index.js: -------------------------------------------------------------------------------- 1 | describe('utils', function() { 2 | require('./utils/defineNameProperty.js') 3 | }) 4 | 5 | describe('strategies', function() { 6 | require('./strategies/WeightedStrategy.js') 7 | }) 8 | 9 | describe('task', function() { 10 | require('./tasks/ForkTask.js') 11 | require('./tasks/ScriptTask.js') 12 | require('./tasks/CallableTask.js') 13 | }) 14 | 15 | describe('worker', function() { 16 | require('./workers/Worker.js') 17 | require('./workers/QueueWorker.js') 18 | require('./workers/CloudWorker.js') 19 | }) 20 | -------------------------------------------------------------------------------- /usecases/gulp/worker.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var relieve = require('../../index.js') 3 | var ScriptTask = relieve.tasks.ScriptTask 4 | var QueueWorker = relieve.workers.QueueWorker 5 | var argv = require('minimist')(process.argv.slice(2)); 6 | 7 | let worker = new QueueWorker({concurrency: 2}) 8 | 9 | for(let i in argv._) { 10 | let task = new ScriptTask(__dirname + '/task.js') 11 | task.name = argv._[i] 12 | task.arguments = [task.name] 13 | worker.add(task) 14 | } 15 | 16 | worker.run() 17 | .then(function() { 18 | process.exit(0) 19 | }) 20 | -------------------------------------------------------------------------------- /packages/relieve-failsafe/src/constants.js: -------------------------------------------------------------------------------- 1 | const os = require('os').platform() 2 | const tmpdir = require('os').tmpdir() 3 | const extend = require('util')._extend 4 | const constants = { 5 | SOCKET: os === 'win32' ? `\\\\?\\pipe\\${tmpdir}\\relieve.sock`: `${tmpdir}/relieve.sock`, 6 | //this keeps a number of alive sockets in case of failure wait until they come back or timeout 7 | PERSISTENCE: `${tmpdir}/relieve.count`, 8 | TIMEOUT: 5000 //timeout to wait before resolving the tcpee group 9 | } 10 | 11 | module.exports = function(options) { 12 | return extend(constants, options) 13 | } 14 | -------------------------------------------------------------------------------- /packages/relieve/containers/MonitorContainer.js: -------------------------------------------------------------------------------- 1 | if (!process.relieve) { 2 | require('./CallableContainer.js') 3 | } 4 | 5 | const os = require('os') 6 | const ipc = process.relieve.ipc 7 | 8 | let cpuUsage 9 | let time 10 | 11 | ipc.on('usage', function() { 12 | cpuUsage = process.cpuUsage(cpuUsage) 13 | time = process.hrtime(time) 14 | 15 | let cpuPercent = (100 * (cpuUsage.system / 1000 + cpuUsage.user / 1000) / (time[0] * 1000 + time[1] / 1e6)) 16 | 17 | ipc.send('usage', { 18 | cpu: cpuUsage, 19 | cpuPercent: cpuPercent, 20 | memory: process.memoryUsage(), 21 | pid: process.pid, 22 | uptime: process.uptime() 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /usecases/compress/task.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var archiver = require('archiver') 3 | var fs = require('fs') 4 | var channel 5 | 6 | module.exports = { 7 | setChannel: function(channel) { 8 | var archive = archiver('zip') 9 | var action = process.argv[3] 10 | var output = fs.createWriteStream(action.dest) 11 | 12 | output.on('close', function() { 13 | channel.send('finish', archive.pointer() + ' bytes written') 14 | process.exit(0) //Note that we exit this tasks when it's done 15 | }) 16 | 17 | archive.on('error', function(err) { throw err }) 18 | archive.pipe(output) 19 | archive.bulk([action.src]) 20 | archive.finalize() 21 | } 22 | } 23 | 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "relieve", 3 | "version": "2.2.1", 4 | "description": "Ease the implementation of multi processing accross your microservices", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "lerna run test", 8 | "coverage": "lerna run coverage" 9 | }, 10 | "engines": { 11 | "node": ">=6.0.0" 12 | }, 13 | "repository": "http://github.com/soyuka/relieve", 14 | "keywords": [ 15 | "child", 16 | "process", 17 | "fork", 18 | "worker", 19 | "pool", 20 | "thread", 21 | "task" 22 | ], 23 | "author": "Antoine Bluchet ", 24 | "license": "MIT", 25 | "devDependencies": { 26 | "lerna": "2.0.0-beta.38" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/relieve/utils/listenersPropagation.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const replicas = [ 4 | 'on', 'once', 'addListener', 'removeListener', 'off', 'removeAllListeners' 5 | ] 6 | 7 | /** 8 | * Mimics the EventEmitter2 methods and replicates them through custom middlewares 9 | * it is used to listen on multiple event emitters (tasks) instead of the main one (worker) 10 | * @param {Function} Fn The prototype where name will be added 11 | * @param {Function} middleware The propagation mechanism 12 | */ 13 | function listenersPropagation(Fn, middleware) { 14 | for(let i in replicas) { 15 | Fn.prototype[replicas[i]] = middleware(replicas[i]) 16 | } 17 | } 18 | 19 | module.exports = listenersPropagation 20 | -------------------------------------------------------------------------------- /usecases/cksfv/readLine.js: -------------------------------------------------------------------------------- 1 | var Promise = require('bluebird') 2 | var crc = require('crc') 3 | var fs = Promise.promisifyAll(require('fs')) 4 | var p = require('path') 5 | 6 | function readLine(path, line) { 7 | if(!line.trim()) 8 | return Promise.resolve() 9 | 10 | var original = line.trim().slice(-8) 11 | var filepath = line.slice(0, -9).trim() 12 | var resp = {original: original, filepath: filepath} 13 | 14 | console.log('Processsing %s', p.resolve(p.dirname(path), filepath)) 15 | 16 | return fs.readFileAsync(p.resolve(p.dirname(path), filepath)) 17 | .then(function(buffer) { 18 | var str = crc.crc32(buffer).toString(16) 19 | 20 | while(str.length < 8) { 21 | str = '0'+str 22 | } 23 | 24 | resp.calculate = str 25 | 26 | return resp 27 | }) 28 | } 29 | 30 | module.exports = readLine 31 | -------------------------------------------------------------------------------- /usecases/cksfv/noWorker.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var Promise = require('bluebird') 3 | var fs = Promise.promisifyAll(require('fs')) 4 | var p = require('path') 5 | var eol = require('os').EOL 6 | var assert = require('assert') 7 | var readLine = require('./readLine.js') 8 | 9 | assert.ok( 10 | typeof process.argv[2] == 'string' && p.extname(process.argv[2]) == '.sfv', 11 | 'Sfv file must be provided' 12 | ); 13 | 14 | var path = process.argv[2] 15 | 16 | console.time('cksfv') 17 | 18 | fs.readFileAsync(path) 19 | .then(function(data) { 20 | data = data.toString() 21 | .split(eol) 22 | .map(e => e.trim()) 23 | .filter(e => e.length) 24 | 25 | return Promise.map(data, function(line) { 26 | return readLine(path, line) 27 | }, {concurrency: 20}) 28 | .then(function(res) { 29 | console.log(res); 30 | console.timeEnd('cksfv') 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /packages/relieve/utils/defineNameProperty.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const uuid = require('uuid') 3 | 4 | /** 5 | * Registers getter and setter for `.name` property on the given prototype 6 | * The default name will be a generated uuid 7 | * 8 | * The setter sets the name once! 9 | * @param {Function} Fn The prototype where name will be added 10 | */ 11 | function defineNameProperty(Fn) { 12 | Object.defineProperty(Fn.prototype, 'name', { 13 | get: function getName() { 14 | if(this._name === undefined) { 15 | this._name = uuid.v4() 16 | } 17 | 18 | return this._name 19 | }, 20 | set: function setName(name) { 21 | if(this._name !== undefined) { 22 | throw new TypeError('Name can be set only once') 23 | } 24 | 25 | this._name = name 26 | this._nameGenerated = false 27 | } 28 | }) 29 | } 30 | 31 | module.exports = defineNameProperty 32 | -------------------------------------------------------------------------------- /packages/relieve-logger/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "relieve-logger", 3 | "version": "2.2.3", 4 | "description": "Relieve Logger interface", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha --exit" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+ssh://git@github.com/soyuka/relieve-logger.git" 12 | }, 13 | "keywords": [ 14 | "relieve", 15 | "process", 16 | "microservice" 17 | ], 18 | "author": "soyuka", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/soyuka/relieve-logger/issues" 22 | }, 23 | "homepage": "https://github.com/soyuka/relieve-logger#readme", 24 | "dependencies": { 25 | "@soyuka/exists": "^1.0.1", 26 | "bluebird": "^3.4.6", 27 | "moment": "^2.15.0" 28 | }, 29 | "devDependencies": { 30 | "chai": "^3.5.0", 31 | "mocha": "^5.2.0" 32 | }, 33 | "peerDependencies": { 34 | "relieve": "^2.1.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/relieve/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "relieve", 3 | "version": "2.2.3", 4 | "description": "Ease the implementation of multi processing accross your microservices", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha", 8 | "coverage": "istanbul cover _mocha" 9 | }, 10 | "engines": { 11 | "node": ">=6.0.0" 12 | }, 13 | "repository": "http://github.com/soyuka/relieve", 14 | "keywords": [ 15 | "child", 16 | "process", 17 | "fork", 18 | "worker", 19 | "pool", 20 | "thread", 21 | "task" 22 | ], 23 | "author": "Antoine Bluchet ", 24 | "license": "MIT", 25 | "dependencies": { 26 | "bluebird": "^3.4.0", 27 | "debug": "^2.2.0", 28 | "eventemitter2": "^2.0.0", 29 | "ipcee": "^1.0.6", 30 | "uuid": "^2.0.2" 31 | }, 32 | "devDependencies": { 33 | "chai": "^3.5.0", 34 | "lerna": "2.0.0-beta.38", 35 | "mocha": "^5.2.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /usecases/compress/worker.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var relieve = require('../../index.js') 3 | var ScriptTask = relieve('tasks').ScriptTask 4 | var QueueWorker = relieve('workers').QueueWorker 5 | 6 | var worker = new QueueWorker({concurrency: 10}) 7 | 8 | //assuming that I have a request for 5 compressions 9 | var actions = [ 10 | {src: ['fixtures/somepath', 'fixtures/somedir/'], dest: 'one.zip'}, 11 | {src: ['fixtures/foo'], dest: 'two.zip'}, 12 | {src: ['fixtures/bar'], dest: 'three.zip'}, 13 | {src: ['fixtures/video.mkv', 'fixtures/audio.mp3'], dest: 'four.zip'}, 14 | {src: ['fixtures/directory/*'], dest: 'five.zip'}, 15 | ] 16 | 17 | for(let i in actions) { 18 | var task = new ScriptTask(__dirname + '/task.js') 19 | task.name = actions[i].dest 20 | task.arguments = [actions[i]] 21 | 22 | worker.add(task) 23 | } 24 | 25 | worker.run() 26 | .then(function() { 27 | for(let t of worker.tasks) { 28 | worker.remove(t.name) 29 | } 30 | }) 31 | -------------------------------------------------------------------------------- /packages/relieve/test/utils/defineNameProperty.js: -------------------------------------------------------------------------------- 1 | var defineNameProperty = require(src + '/utils/defineNameProperty.js') 2 | describe('defineNameProperty', function() { 3 | it('should register name getter/setter', function() { 4 | function Task() {} 5 | 6 | defineNameProperty(Task) 7 | 8 | var task = new Task() 9 | task.name = 'test' 10 | 11 | expect(task.name).to.equal('test') 12 | }) 13 | 14 | it('should get default name', function() { 15 | function Task() {} 16 | 17 | defineNameProperty(Task) 18 | 19 | var task = new Task() 20 | 21 | expect(task.name).not.to.be.undefined 22 | }) 23 | 24 | it('should fail setting name twice', function() { 25 | function Task() {} 26 | 27 | defineNameProperty(Task) 28 | 29 | var task = new Task() 30 | task.name = 'test' 31 | 32 | try { 33 | task.name = 'test' 34 | } catch(e) { 35 | expect(e).to.be.an.instanceof(TypeError) 36 | } 37 | }) 38 | }) 39 | -------------------------------------------------------------------------------- /usecases/socket/worker.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var relieve = require('../../index.js') 3 | var CallableTask = relieve.tasks.CallableTask 4 | var Worker = relieve.workers.CloudWorker 5 | var net = require('net') 6 | 7 | const RANDOM_MIN = 1 8 | const RANDOM_MAX = 156 //78 iterations until Number.MAX_SAFE_INTEGER 9 | 10 | var worker = new Worker() 11 | 12 | let i = 0 13 | let len = 4 14 | for (; i < len; i++) { 15 | let task = new CallableTask(__dirname + '/task.js', {restart: true}) 16 | task.name = 'task'+i 17 | worker.add(task) 18 | } 19 | 20 | worker.run() 21 | 22 | var server = net.createServer() 23 | 24 | server.on('connection', function(socket) { 25 | worker.send('socket', socket) 26 | .then(function(task) { 27 | let n = Math.floor(Math.random() * (RANDOM_MAX - RANDOM_MIN + 1)) + RANDOM_MIN 28 | task.call('doHeavyStuff', n) 29 | }) 30 | }) 31 | 32 | server.listen(function() { 33 | console.log('server listening on %j', server.address()); 34 | }) 35 | -------------------------------------------------------------------------------- /packages/relieve/tasks/ForkTask.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const IPCEE = require('ipcee') 3 | const util = require('util') 4 | const defineNameProperty = require('../utils/defineNameProperty.js') 5 | 6 | /** 7 | * A basic Fork task 8 | * @module tasks/ForkTask 9 | * @tutorial ForkTask 10 | */ 11 | 12 | /** 13 | * @class 14 | * @param {Childprocess#fork} fork a forked script 15 | * @property {String} [name=uuid.v4()] the task name 16 | */ 17 | function ForkTask(fork) { 18 | if(!(this instanceof ForkTask)) { return new ForkTask(fork) } 19 | 20 | IPCEE.call(this, fork, {wildcard: false}) 21 | } 22 | 23 | util.inherits(ForkTask, IPCEE) 24 | defineNameProperty(ForkTask) 25 | 26 | /** 27 | * Wrapper to child_process.kill(signal) 28 | * {@link https://nodejs.org/api/process.html#process_process_kill_pid_signal} 29 | * @param {Number} signal 30 | * @return void 31 | */ 32 | ForkTask.prototype.kill = function(signal) { 33 | this.client.kill(signal) 34 | } 35 | 36 | module.exports = ForkTask 37 | -------------------------------------------------------------------------------- /packages/relieve-failsafe/src/master.js: -------------------------------------------------------------------------------- 1 | const TCPEEServer = require('./tcpee/server') 2 | const debug = require('debug')('relieve-failsafe:master') 3 | const fs = require('fs') 4 | const constants = require('./constants') 5 | 6 | let tcpeeGroup = null 7 | 8 | ;['SIGTERM', 'SIGINT'].map(e => { 9 | process.on(e, function() { 10 | process.exit() 11 | }) 12 | }) 13 | 14 | module.exports = function(options) { 15 | if (tcpeeGroup !== null) { 16 | return Promise.resolve(tcpeeGroup) 17 | } 18 | 19 | options = constants(options) 20 | 21 | process.on('exit', function() { 22 | fs.writeFileSync(options.PERSISTENCE, tcpeeGroup === null ? 0 : tcpeeGroup.clients.size) 23 | }) 24 | 25 | let num = 0 26 | 27 | try { 28 | num = fs.readFileSync(options.PERSISTENCE) 29 | } catch(e) {} 30 | 31 | return TCPEEServer(options, parseInt(num.toString())) 32 | .then((t) => { 33 | debug('master ready') 34 | tcpeeGroup = t 35 | return Promise.resolve(tcpeeGroup) 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /packages/relieve-failsafe/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "relieve-failsafe", 3 | "version": "2.2.5", 4 | "description": "Relieve Fail Safe interface", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "test": "mocha -b test/index.js --exit" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+ssh://git@github.com/soyuka/relieve-failsafe.git" 12 | }, 13 | "keywords": [ 14 | "relieve", 15 | "process", 16 | "microservice" 17 | ], 18 | "author": "soyuka", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/soyuka/relieve-failsafe/issues" 22 | }, 23 | "homepage": "https://github.com/soyuka/relieve-failsafe#readme", 24 | "devDependencies": { 25 | "chai": "^3.5.0", 26 | "mocha": "^5.2.0", 27 | "relieve": "^2.2.3" 28 | }, 29 | "peerDependencies": { 30 | "relieve": "^2.2.3" 31 | }, 32 | "dependencies": { 33 | "@soyuka/exists-sync": "^1.0.1", 34 | "debug": "^2.3.2", 35 | "tcpee": "^1.0.1" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/2-ScriptTask.md: -------------------------------------------------------------------------------- 1 | A [Script Task]{@link module:tasks/ScriptTask~ScriptTask} handles the fork and only takes a nodejs script path. 2 | 3 | This script will then run in a [ScriptContainer]{@link module:tasks/ScriptContainer~ScriptContainer}. The container is used to pre-register some events and fires: 4 | - `start` when the container is ready 5 | - `error` when `uncaughtException` occurs 6 | 7 | For example using a small http server: 8 | 9 | ```javascript 10 | //server.js 11 | var http = require('http') 12 | 13 | http.createServer(function(req, res) { 14 | res.writeHead(200) 15 | res.end("hello world\n") 16 | }).listen(8020) 17 | ``` 18 | 19 | The worker: 20 | ```javascript 21 | //worker.js 22 | var ScriptTask = require('relieve/tasks/ScriptTask') 23 | 24 | var task = new ScriptTask('server.js', {restart: true}) 25 | 26 | task.start() 27 | ``` 28 | 29 | The [Callable Task]{@tutorial 3-CallableTask} enables advance interactions. It extends the [Script Task]{@link module:tasks/ScriptTask~ScriptTask}. 30 | -------------------------------------------------------------------------------- /packages/relieve-logger/README.md: -------------------------------------------------------------------------------- 1 | relieve-logger 2 | ============== 3 | 4 | ## Install 5 | 6 | ``` 7 | npm install relieve-logger --save 8 | ``` 9 | 10 | ## Usage 11 | 12 | To use with [relieve](https://github.com/soyuka/relieve) 13 | 14 | ```javascript 15 | var Logger = require('relieve-logger') 16 | var CallableTask = require('relieve/tasks/CallableTask') 17 | 18 | var logger = new Logger(`out.log`, `err.log`) 19 | 20 | var t = new CallableTask('test.js', { 21 | interfaces: [logger] 22 | }) 23 | ``` 24 | 25 | ## API 26 | 27 | ``` 28 | /** 29 | * @param {string} out - the out file path or a writable stream 30 | * @param {string} err - the out file path or a writable stream 31 | * @param {Object} options 32 | * options.delay - the delay you want to rotate for example `1d`, `1h`(moment format http://momentjs.com/docs/#/manipulating/add/) 33 | * options.size - the max size in bytes before rotation 34 | * Default has no rotation 35 | */ 36 | new Logger(out, err, options) 37 | ``` 38 | 39 | ## Licence 40 | 41 | MIT 42 | -------------------------------------------------------------------------------- /usecases/socket/task.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | function fibonacci(max) { 3 | let x = -1; 4 | let i = 0; 5 | let j = 1; 6 | let k = 0; 7 | 8 | for(; k < max; i = j, j = x, k++) { 9 | 10 | if(x > Number.MAX_SAFE_INTEGER) { 11 | console.error('Fibonacci stopeed at iteration %d', k); 12 | return {number: x, iterations: k, error: 'Number exceed the limit ('+Number.MAX_SAFE_INTEGER+')'} 13 | } 14 | 15 | x = i + j 16 | } 17 | 18 | return {number: x, iterations: k} 19 | } 20 | 21 | var socks = [] 22 | 23 | module.exports = { 24 | setChannel: function(channel) { 25 | channel.on('socket', function(socket) { 26 | socks.push(socket) 27 | }) 28 | }, 29 | doHeavyStuff: function(num) { 30 | let sock = socks.shift() 31 | let f = fibonacci(num) 32 | if(f.error) { 33 | sock.write('Fibonnacci errored with message: \n') 34 | sock.write(f.error + '\n') 35 | sock.end() 36 | return 37 | } 38 | 39 | sock.write(`Fibonnacci result for ${num} is ${f.number}\n`) 40 | sock.write(`${f.iterations} iterations done\n`) 41 | sock.end() 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /examples/1-ForkTask.md: -------------------------------------------------------------------------------- 1 | A [Fork Task]{@link module:tasks/ForkTask~ForkTask} implements a simple task from an existing [fork](https://nodejs.org/api/child_process.html#child_process_child_process_fork_modulepath_args_options) object. 2 | 3 | The Fork task: 4 | 5 | ```javascript 6 | //task.js 7 | process.send('start') 8 | ``` 9 | 10 | This would be the task worker: 11 | 12 | ```javascript 13 | //worker.js 14 | var ForkTask = require('relieve/tasks/ForkTask') 15 | var fork = require('child_process').fork 16 | 17 | var task_fork = fork('task.js') 18 | 19 | task = new ForkTask(task_fork) 20 | task.name = 'MyForkTask' 21 | 22 | task.once('start', function() { 23 | //task started 24 | }) 25 | ``` 26 | 27 | Another example using IPCEE to ease the communication: 28 | 29 | ```javascript 30 | //task.js 31 | var IPCEE = require('relieve/IPCEE') 32 | 33 | var ipc = IPCEE(process) 34 | 35 | ipc.on('thank', function(person) { 36 | if(person == 'you') 37 | ipc.send('you', 'welcome') 38 | }) 39 | ``` 40 | 41 | The worker: 42 | 43 | ```javascript 44 | //worker.js 45 | var ForkTask = require('relieve/tasks/ForkTask') 46 | var fork = require('child_process').fork 47 | 48 | task = new ForkTask(fork('task.js')) 49 | 50 | //wrapper.js 51 | task.send('thank', 'you') 52 | 53 | task.once('you', function(data) { 54 | //data is welcome 55 | }) 56 | ``` 57 | -------------------------------------------------------------------------------- /packages/relieve/test/tasks/ForkTask.js: -------------------------------------------------------------------------------- 1 | var Task = require(src + '/tasks/ForkTask.js') 2 | var fork = require('child_process').fork 3 | var p = require('path') 4 | var task 5 | 6 | describe('ForkTask', function() { 7 | 8 | it('should throw because ipc is not available', function() { 9 | try { 10 | new Task({foo: 'bar'}) 11 | } catch(err) { 12 | expect(err.message).to.equal('IPC is not enabled') 13 | } 14 | }) 15 | 16 | it('should create a new without constructor', function() { 17 | var task_fork = fork(p.join(__dirname, '../fixtures/answer.js')) 18 | task = Task(task_fork) 19 | }) 20 | 21 | it('should create a new task', function() { 22 | var task_fork = fork(p.join(__dirname, '../fixtures/answer.js')) 23 | task = new Task(task_fork) 24 | task.name = 'test' 25 | }) 26 | 27 | it('should get message through task', function(cb) { 28 | task.once('test', function(x, y) { 29 | expect(x).to.deep.equal({foo: 'bar'}) 30 | expect(y).to.deep.equal([0,1,2]) 31 | cb() 32 | }) 33 | 34 | task.send('test', {foo: 'bar'}, [0,1,2]) 35 | }) 36 | 37 | it('should not be available because child has been killed', function(cb) { 38 | task.kill() 39 | 40 | task.once('exit', function() { 41 | process.nextTick(function() { 42 | expect(task.client).to.be.undefined 43 | cb() 44 | }) 45 | }) 46 | 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /packages/relieve/containers/ScriptContainer.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /** 3 | * Script container 4 | * @module containers/ScriptContainer 5 | */ 6 | 7 | if (!process.relieve || process.relieve.ipc === undefined) { 8 | require('./IPCContainer') 9 | } 10 | 11 | const {argv, containerArgs, ipc} = process.relieve 12 | 13 | ipc.once('$RELIEVE_REQUIRE', function() { 14 | let script = require(argv[0]) 15 | 16 | if(typeof script == 'function') { 17 | script = new script 18 | } 19 | 20 | process.relieve.script = script 21 | 22 | if(typeof script == 'object' && typeof script.setChannel == 'function') { 23 | console.error('setChannel: deprecated method call, use start instead or access the channel through process.relieve.ipc') 24 | script.setChannel(ipc) 25 | } 26 | 27 | if(typeof script == 'object' && typeof script.start == 'function') { 28 | script.start() 29 | } 30 | 31 | /** 32 | * @listens module:process#uncaughtException 33 | */ 34 | function errorCaught(err) { 35 | console.error(err.stack) 36 | 37 | /** 38 | * @fires error 39 | */ 40 | ipc.send('error', err.toString(), err.stack) 41 | 42 | process.nextTick(() => process.exit(1)) 43 | } 44 | 45 | ;['uncaughtException', 'unhandledRejection'].map(e => process.on(e, errorCaught)) 46 | 47 | containerArgs.containers.map(e => require(e)) 48 | 49 | const startedAt = Date.now() 50 | ipc.send('start', startedAt) 51 | }) 52 | -------------------------------------------------------------------------------- /examples/4-Worker.md: -------------------------------------------------------------------------------- 1 | A [Worker]{@link module:workers/Worker~Worker} allows to manage multiple tasks. This Worker covers only basic features and it is the base for the [CloudWorker]{@link module:workers/CloudWorker~CloudWorker} and the [QueueWorker]{@link module:workers/QueueWorker~QueueWorker}. 2 | 3 | For example, having a simple task that answer to my messages: 4 | 5 | ```javascript 6 | //answer.js 7 | process.on('message', function(d) { 8 | process.send(d) 9 | }) 10 | ``` 11 | 12 | My worker will handle a couple of tasks: 13 | 14 | ```javascript 15 | //worker.js 16 | var Worker = require('relieve/workers/Worker') 17 | var Task = require('relieve/tasks/ForkTask') 18 | 19 | var task1 = new Task(fork('answer.js')) 20 | task1.name = 'task1' 21 | var task2 = new Task(fork('answer.js')) 22 | task2.name = 'task2' 23 | 24 | worker.add(task1).add(task2) 25 | 26 | //send a message to every task 27 | worker.send('message', 'hello world') 28 | //Promise resolves when every message has reach the task 29 | .then(function() { 30 | worker.kill() 31 | }) 32 | ``` 33 | 34 | The worker also allows to retreive tasks: 35 | 36 | ```javascript 37 | //by name 38 | worker.task('task1').send('message', 'hello world') 39 | 40 | //or through a Map 41 | for(let t of worker.tasks.values()) { 42 | t.send('message', 'hello world') 43 | } 44 | ``` 45 | 46 | See more advanced use cases with the [QueueWorker]{@tutorial 5-QueueWorker} or the [CloudWorker]{@tutorial 6-CloudWorker}. 47 | -------------------------------------------------------------------------------- /packages/relieve-failsafe/src/container.js: -------------------------------------------------------------------------------- 1 | const Socket = require('net').Socket 2 | const TCPEE = require('tcpee') 3 | const startedAt = Date.now() 4 | 5 | require('relieve/containers/ArgumentsContainer') 6 | 7 | let interval 8 | function connect() { 9 | socket.connect(process.relieve.containerArgs.socket) 10 | } 11 | 12 | const socket = new Socket({allowHalfOpen: true}) 13 | 14 | socket.on('error', function(e) { 15 | if (e.code === 'ENOENT' || e.code === 'ECONNRESET' || e.code === 'ECONNREFUSED') { 16 | setTimeout(connect, 1000) 17 | } 18 | }) 19 | 20 | socket.on('end', function() { 21 | interval = process.nextTick(connect) 22 | }) 23 | 24 | connect() 25 | 26 | const ipc = new TCPEE(socket, process.relieve.containerArgs.eventemitter) 27 | 28 | process.relieve.ipc = ipc 29 | 30 | ipc.on('error', function(err, stack) { 31 | console.error('Socket error', err, stack) 32 | }) 33 | 34 | ipc.on('startedAt', function() { 35 | ipc.send('startedAt', startedAt) 36 | }) 37 | 38 | ipc.on('$TCPEE_IDENTITY', function() { 39 | ipc.send('$TCPEE_IDENTITY', process.relieve.containerArgs.identity) 40 | }) 41 | 42 | ipc.on('$TCPEE_KILL', function(signal) { 43 | process.kill(process.pid, signal) 44 | }) 45 | 46 | process.on('exit', function(code) { 47 | ipc.send('$TCPEE_EXIT', code) 48 | 49 | process.nextTick(e => process.exit(code)) 50 | }) 51 | 52 | ;['SIGTERM', 'SIGINT'].map(e => { 53 | process.on(e, process.exit) 54 | }) 55 | 56 | require('relieve/containers/ScriptContainer') 57 | -------------------------------------------------------------------------------- /packages/relieve-failsafe/src/tcpee/group.js: -------------------------------------------------------------------------------- 1 | const TCPEE = require('tcpee') 2 | const util = require('util') 3 | const EE = require('events') 4 | 5 | function TCPEEGroup(options) { 6 | if(!(this instanceof TCPEEGroup)) { return new TCPEEGroup(options) } 7 | 8 | this.options = options 9 | this.clients = new Map() 10 | this._ready = false 11 | 12 | EE.call(this, options) 13 | } 14 | 15 | util.inherits(TCPEEGroup, EE) 16 | 17 | TCPEEGroup.prototype.add = function(sock) { 18 | let tcpee = new TCPEE(sock, this.options) 19 | 20 | tcpee.client.once('close', () => { 21 | this.clients.delete(tcpee.$TCPEE_IDENTITY) 22 | }) 23 | 24 | tcpee.on('error', function(error, stack) { 25 | console.error('Got an error on the remote TCPEE socket') 26 | console.error(error, stack) 27 | }) 28 | 29 | return new Promise((resolve, reject) => { 30 | tcpee.send('$TCPEE_IDENTITY') 31 | tcpee.once('$TCPEE_IDENTITY', (identity) => { 32 | tcpee.$TCPEE_IDENTITY = identity 33 | this.clients.set(identity, tcpee) 34 | this.emit('$TCPEE_ADD:'+identity, tcpee) 35 | resolve(tcpee) 36 | }) 37 | }) 38 | } 39 | 40 | TCPEEGroup.prototype.get = function(identity) { 41 | return this.clients.get(identity) 42 | } 43 | 44 | TCPEEGroup.prototype.ready = function() { 45 | this._ready = true 46 | this.emit('ready') 47 | } 48 | 49 | TCPEEGroup.prototype.destroy = function() { 50 | for (let [key, value] of this.clients) { 51 | value.client.destroy() 52 | } 53 | } 54 | 55 | module.exports = TCPEEGroup 56 | -------------------------------------------------------------------------------- /packages/relieve-failsafe/test/group.js: -------------------------------------------------------------------------------- 1 | const net = require('net') 2 | const expect = require('chai').expect 3 | const existsSync = require('@soyuka/exists-sync') 4 | const fs = require('fs') 5 | const TCPEEGroup = require('../src/tcpee/group') 6 | const TCPEE = require('tcpee') 7 | 8 | const os = require('os').platform() 9 | const SOCKET = os === 'win32' ? `\\\\?\\pipe\\${__dirname}\\group.sock`: `${__dirname}/group.sock` 10 | 11 | describe('group', function() { 12 | it('should add a tcpee', function(cb) { 13 | const group = new TCPEEGroup() 14 | let i = 0 15 | 16 | if (os !== 'win32' && existsSync(SOCKET)) { 17 | fs.unlinkSync(SOCKET) 18 | } 19 | 20 | let server = new net.Server() 21 | server.listen(SOCKET) 22 | 23 | group.on('$TCPEE_ADD:foo', function() { 24 | i++ 25 | }) 26 | 27 | server.on('connection', function(socket) { 28 | group.add(socket) 29 | .then(e => { 30 | expect(e).to.be.an.instanceof(TCPEE) 31 | 32 | expect(group.get('foo')).to.deep.equal(e) 33 | expect(i).to.equal(1) 34 | expect(group.clients.size).to.equal(1) 35 | 36 | cb() 37 | }) 38 | }) 39 | 40 | let socket = new net.Socket() 41 | let client = new TCPEE(socket) 42 | 43 | client.on('$TCPEE_IDENTITY', function() { 44 | client.send('$TCPEE_IDENTITY', 'foo') 45 | }) 46 | 47 | socket.connect(SOCKET) 48 | }) 49 | 50 | after(function() { 51 | if (os !== 'win32' && existsSync(SOCKET)) { 52 | fs.unlinkSync(SOCKET) 53 | } 54 | }) 55 | }) 56 | -------------------------------------------------------------------------------- /packages/relieve/test/strategies/WeightedStrategy.js: -------------------------------------------------------------------------------- 1 | var strategy = require(src + '/strategies/WeightedStrategy.js') 2 | var Promise = require('bluebird') 3 | var t0, t1 4 | 5 | describe('Strategy', function() { 6 | it('should add some values', function() { 7 | Promise.all([ 8 | strategy.push('test1'), 9 | strategy.push('test2'), 10 | strategy.push('test3') 11 | ]) 12 | }) 13 | 14 | it('should get the next value', function() { 15 | return strategy.next() 16 | .then(function(v) { 17 | expect(v).not.to.be.undefined 18 | t0 = v 19 | return strategy.start(v) 20 | }) 21 | }) 22 | 23 | it('should get the next value', function() { 24 | return strategy.next() 25 | .then(function(v) { 26 | expect(v).not.to.be.undefined 27 | expect(v).not.to.equal(t0) 28 | t1 = v 29 | 30 | return Promise.all([strategy.start(v), strategy.end(t0)]) 31 | }) 32 | }) 33 | 34 | it('should get the next value', function() { 35 | return strategy.next() 36 | .then(function(v) { 37 | expect(v).not.to.be.undefined 38 | expect(v).not.to.equal(t1) 39 | return strategy.start(v) 40 | }) 41 | }) 42 | 43 | it('should get test1', function() { 44 | expect(strategy.get('test1')).to.deep.equal({score: 0, name: 'test1'}) 45 | }) 46 | 47 | it('should get nothing', function() { 48 | expect(strategy.get('nothing')).to.be.undefined 49 | }) 50 | 51 | it('should remove strategy', function() { 52 | strategy.remove('test1') 53 | 54 | expect(strategy.get('test1')).to.be.undefined 55 | }) 56 | }) 57 | -------------------------------------------------------------------------------- /packages/relieve-failsafe/README.md: -------------------------------------------------------------------------------- 1 | # Relieve-failsafe [![Build Status](https://travis-ci.org/soyuka/relieve-failsafe.svg?branch=master)](https://travis-ci.org/soyuka/relieve-failsafe) 2 | 3 | Relieve failsafe interface 4 | 5 | This interface allows you to launch independent microservices that will maintain an open channel with a master process. This master process can die safely, as the child process will stay alive and reconnect when he can. 6 | 7 | This is still an experimental module! 8 | 9 | ## Usage 10 | 11 | If you're not familiar with the `relieve` library, please check out it's usage first [here](https://github.com/soyuka/relieve). 12 | 13 | ``` 14 | npm install relieve relieve-failsafe 15 | ``` 16 | 17 | Task: 18 | 19 | ```javascript 20 | // task.js 21 | const ipc = process.relieve.ipc 22 | 23 | module.exports = { 24 | start: function() { 25 | ipc.on('ping', function() { 26 | ipc.send('pong') 27 | }) 28 | 29 | setInterval(e => { 30 | console.log('Still alive') 31 | }, 1000) 32 | } 33 | } 34 | ``` 35 | 36 | Master: 37 | 38 | ```javascript 39 | // master.js 40 | const ScriptTask = require('relieve/tasks/ScriptTask') 41 | const FailSafe = require('relieve-failsafe') 42 | 43 | const task = new ScriptTask('task.js', { 44 | interfaces: [new FailSafe()] 45 | }) 46 | 47 | task.start() 48 | .then(() => { 49 | task.on('pong', () => { 50 | console.log('got pong!') 51 | }) 52 | 53 | task.send('ping') 54 | }) 55 | ``` 56 | 57 | You can test this example by cloning this repository and by launching `node examples/master`. The task will have to be killed manually. 58 | -------------------------------------------------------------------------------- /usecases/cksfv/worker.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var relieve = require('../../index.js') 3 | var assert = require('assert') 4 | var Promise = require('bluebird') 5 | var p = require('path') 6 | var fs = Promise.promisifyAll(require('fs')) 7 | var eol = require('os').EOL 8 | var CallableTask = relieve('tasks').CallableTask 9 | var QueueWorker = relieve('workers').QueueWorker 10 | 11 | assert.ok( 12 | typeof process.argv[2] == 'string' && p.extname(process.argv[2]) == '.sfv', 13 | 'Sfv file must be provided' 14 | ); 15 | 16 | var path = process.argv[2] 17 | var map = [] 18 | 19 | function call(line) { 20 | return function() { 21 | worker.task(line).call('readLine', path, line) 22 | } 23 | } 24 | 25 | var worker = new QueueWorker({concurrency: 20}) 26 | 27 | console.time('cksfv') 28 | 29 | fs.readFileAsync(path) 30 | .then(function(data) { 31 | data = data.toString() 32 | .split(eol) 33 | .map(e => e.trim()) 34 | .filter(e => e.length) 35 | 36 | data.map(function(line) { 37 | let task = new CallableTask(__dirname + '/task.js') 38 | task.name = line 39 | task.once('start', call(line)) 40 | task.once('cksfv', function(resp) { 41 | map.push(resp) 42 | }) 43 | worker.add(task) 44 | }) 45 | 46 | worker.run() 47 | .then(function() { 48 | let errors = 0 49 | for(let i in map) { 50 | if(map[i].original != map[i].calculate) { 51 | console.error('File %s does not match crc', map[i].filepath) 52 | errors++ 53 | } 54 | } 55 | 56 | console.log('Done checking with %d errors', errors) 57 | console.timeEnd('cksfv') 58 | }) 59 | }) 60 | -------------------------------------------------------------------------------- /packages/relieve/test/workers/Worker.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var Worker = require(src + '/workers/Worker.js') 3 | var worker = Worker() 4 | var p = require('path') 5 | var Task = require(src + '/tasks/ForkTask.js') 6 | var fork = require('child_process').fork 7 | 8 | var task = new Task(fork(p.join(__dirname, '../fixtures/answer.js'))) 9 | task.name = 'test' 10 | 11 | var task2 = new Task(fork(p.join(__dirname, '../fixtures/answer.js'))) 12 | task2.name = 'test2' 13 | 14 | describe('Worker', function(cb) { 15 | it('should add a task', function() { 16 | worker.add(task) 17 | 18 | expect(worker.task('test')).to.deep.equal(task) 19 | }) 20 | 21 | it('should add a second task and get them all', function() { 22 | worker.add(task2) 23 | 24 | let tasks = worker.tasks 25 | 26 | expect(tasks).to.be.an.instanceof(Map) 27 | expect(tasks.size).to.equal(2) 28 | }) 29 | 30 | it('should send a message to every task', function() { 31 | return worker.send('message', 'hello') 32 | }) 33 | 34 | it('should remove task', function() { 35 | return worker.remove('test') 36 | .then(function() { 37 | expect(worker.task('test')).to.be.undefined 38 | expect(worker.tasks.size).to.equal(1) 39 | }) 40 | }) 41 | 42 | it('should send SIGINT to tasks', function(cb) { 43 | worker.kill() 44 | 45 | worker.once('exit', function() { 46 | expect(worker.tasks.size).to.equal(0) 47 | cb() 48 | }) 49 | }) 50 | 51 | it('should not be able to set tasks', function() { 52 | try { 53 | worker.tasks = {} 54 | } catch(e) { 55 | expect(e).to.be.an.instanceof(ReferenceError) 56 | expect(e.message).to.equal('Property is read-only') 57 | } 58 | }) 59 | }) 60 | -------------------------------------------------------------------------------- /examples/3-CallableTask.md: -------------------------------------------------------------------------------- 1 | The [Callable Task]{@link module:tasks/CallableTask~CallableTask} takes a script path as does the [Script Task]{@tutorial ScriptTask}. It allows the worker to call function, and expect (or not) ans answer. 2 | 3 | Using an [archiver](https://github.com/archiverjs) task that will zip a directory: 4 | 5 | The Task: 6 | 7 | ```javascript 8 | //task.js 9 | var archiver = require('archiver') 10 | var fs = require('fs') 11 | var channel = process.relieve.ipc 12 | var archive 13 | 14 | function archiveDirectory(dir) { 15 | archive = archiver('zip') 16 | var output = fs.createWriteStream('archive.zip') 17 | 18 | output.on('close', function() { 19 | channel.send('finish', archive.pointer() + ' bytes written') 20 | }) 21 | 22 | archive.on('error', function(err) { throw err }) 23 | 24 | archive.pipe(output) 25 | 26 | archive.directory('./', 'dest') 27 | archive.finalize() 28 | } 29 | 30 | module.exports = { 31 | archiveDirectory: archiveDirectory, 32 | bytesWritten: function() { return archive.pointer() } 33 | } 34 | ``` 35 | 36 | The [Callable Task]{@link module:tasks/CallableTask~CallableTask} can call the `archiveDirectory` method. It'll launch an archive and it then calls `bytesWritten` to get back some progression. When the task finishes, we clear the interval and print the final message. 37 | 38 | The Worker: 39 | 40 | ```javascript 41 | //worker.js 42 | var CallableTask = require('relieve/tasks/CallableTask') 43 | 44 | var task = new CallableTask('task.js', {restart: true}) 45 | 46 | task.start() 47 | .then(function() { 48 | task.call('archiveDirectory', './directory') 49 | 50 | var bytesInterval = setInterval(function() { 51 | console.log(task.get('bytesWritten')) 52 | }, 100) 53 | 54 | task.once('finish', function(message) { 55 | console.log(message) 56 | clearInterval(bytesInterval) 57 | }) 58 | }) 59 | ``` 60 | -------------------------------------------------------------------------------- /packages/relieve/strategies/WeightedStrategy.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * The Weighted Strategy for the CloudWorker 5 | * Increments a number when each task gets called, 6 | * the next task will get the one with the lowest score 7 | * 8 | * This strategy resides in memory and resolves promises to be used with 9 | * redis or another Queue/Set score provider 10 | * @module strategies/WeightedStrategy 11 | */ 12 | 13 | module.exports = { 14 | /** @property {Array} Queue */ 15 | queue: [], 16 | /** 17 | * Get the queue element by name 18 | * @param {String} name 19 | * @return {Object} the task 20 | */ 21 | get: function(name) { 22 | return this.queue.find(e => e.name === name) 23 | }, 24 | /** 25 | * Adds a task to the queue 26 | * @param {String} name 27 | * @return {Promise} 28 | */ 29 | push: function(name) { 30 | this.queue.push({score: 0, name: name}) 31 | return Promise.resolve() 32 | }, 33 | /** 34 | * Removes a task from the queue by name 35 | * @param {String} Name 36 | */ 37 | remove: function(name) { 38 | let i = this.queue.findIndex(e => e.name === name) 39 | 40 | if(~i) 41 | this.queue.splice(i, 1) 42 | }, 43 | /** 44 | * Starts a task, increments the score 45 | * @param {String} name 46 | * @return {Promise} 47 | */ 48 | start: function(name) { 49 | this.get(name).score++ 50 | return Promise.resolve() 51 | }, 52 | /** 53 | * Ends a task, decrements the score 54 | * @param {String} name 55 | * @return {Promise} 56 | */ 57 | end: function(name) { 58 | this.get(name).score-- 59 | return Promise.resolve() 60 | }, 61 | /** 62 | * Returns the next available task 63 | * @return {Promise} Resolves the task name 64 | */ 65 | next: function() { 66 | this.queue.sort((a, b) => a.score - b.score) 67 | 68 | return Promise.resolve(this.queue[0].name) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /packages/relieve/containers/CallableContainer.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const assert = require('assert') 3 | const p = require('path') 4 | 5 | /** 6 | * Callable script container 7 | * @module containers/CallableContainer 8 | */ 9 | 10 | /** 11 | * @contant {String} SCRIPT_OBJECT_ERROR 12 | */ 13 | const SCRIPT_OBJECT_ERROR = 'Script is not an object' 14 | 15 | if (!process.relieve) { 16 | require('./ScriptContainer.js') 17 | } 18 | 19 | const script = process.relieve.script 20 | const ipc = process.relieve.ipc 21 | 22 | /** 23 | * Adds a stack trace to the error event 24 | * @param {Error} error 25 | * @private 26 | */ 27 | function error(error) { 28 | return ipc.send('error', error.message, error.stack) 29 | } 30 | 31 | /** 32 | * Is used for long jobs, don't expect an answer 33 | * @param {String} method method 34 | * @param {arguments} ...args 35 | * @example 36 | * ipc.send('call', 'method', args...) 37 | */ 38 | function call(method, ...args) { 39 | if(typeof script != 'object') 40 | return error(new Error(SCRIPT_OBJECT_ERROR)) 41 | 42 | if(typeof script[method] != 'function') 43 | return error(new Error(`Method ${method} is not a function`)) 44 | 45 | script[method].apply(script, args) 46 | } 47 | 48 | ipc.on('call', call) 49 | 50 | /** 51 | * Like Call but we send data back 52 | * The parent process listens on the uniqueid-event 53 | * @param {String} key key 54 | * @param {String} method key 55 | * @param {arguments} ...args 56 | * @example 57 | * ipc.send('get', 'uniqueid-event', 'info') 58 | */ 59 | function get(key, method, ...args) { 60 | if(typeof script != 'object') 61 | return error(new Error(SCRIPT_OBJECT_ERROR)) 62 | 63 | if(typeof script[method] == 'function') { 64 | return Promise.resolve(script[method].apply(script, args)) 65 | .then((...args) => { 66 | args.unshift(key) 67 | 68 | ipc.send.apply(ipc, args) 69 | }) 70 | } 71 | 72 | ipc.send.apply(ipc, [key, script[method]]) 73 | } 74 | 75 | ipc.on('get', get) 76 | -------------------------------------------------------------------------------- /packages/relieve-failsafe/test/server.js: -------------------------------------------------------------------------------- 1 | const net = require('net') 2 | const TCPEEServer = require('../src/tcpee/server') 3 | const TCPEEGroup = require('../src/tcpee/group') 4 | const TCPEE = require('tcpee') 5 | const expect = require('chai').expect 6 | const existsSync = require('@soyuka/exists-sync') 7 | const fs = require('fs') 8 | 9 | const os = require('os').platform() 10 | const SOCKET = os === 'win32' ? `\\\\?\\pipe\\${__dirname}\\server.sock`: `${__dirname}/server.sock` 11 | 12 | describe('server', function() { 13 | 14 | afterEach(function() { 15 | return TCPEEServer.close() 16 | }) 17 | 18 | it('should start without waiting for connections', function(cb) { 19 | TCPEEServer({SOCKET: SOCKET}) 20 | .then(tcpeeGroup => { 21 | expect(tcpeeGroup).to.be.an.instanceof(TCPEEGroup) 22 | expect(tcpeeGroup.clients.size).to.equal(0) 23 | 24 | cb() 25 | }) 26 | }) 27 | 28 | it('should wait for a socket entry before resolving', function(cb) { 29 | TCPEEServer({SOCKET: SOCKET}, 1) 30 | .then(tcpeeGroup => { 31 | expect(tcpeeGroup).to.be.an.instanceof(TCPEEGroup) 32 | expect(tcpeeGroup.get('foo')).to.be.an.instanceof(TCPEE) 33 | expect(tcpeeGroup.clients.size).to.equal(1) 34 | cb() 35 | }) 36 | 37 | let socket = new net.Socket() 38 | let client = new TCPEE(socket) 39 | 40 | client.on('$TCPEE_IDENTITY', function() { 41 | client.send('$TCPEE_IDENTITY', 'foo') 42 | }) 43 | 44 | function connect() { 45 | socket.connect(SOCKET) 46 | } 47 | 48 | connect() 49 | }) 50 | 51 | it('should timeout because socket is not there', function(cb) { 52 | TCPEEServer({SOCKET: SOCKET, TIMEOUT: 100}, 1) 53 | .then(tcpeeGroup => { 54 | expect(tcpeeGroup).to.be.an.instanceof(TCPEEGroup) 55 | expect(tcpeeGroup.clients.size).to.equal(0) 56 | cb() 57 | }) 58 | }) 59 | 60 | after(function() { 61 | if (os !== 'win32' && existsSync(SOCKET)) { 62 | fs.unlinkSync(SOCKET) 63 | } 64 | }) 65 | }) 66 | -------------------------------------------------------------------------------- /packages/relieve/tasks/CallableTask.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const ScriptTask = require('./ScriptTask.js') 3 | const p = require('path') 4 | const uuid = require('uuid') 5 | const util = require('util') 6 | 7 | /** 8 | * A Callable Script Task 9 | * @module tasks/CallableTask 10 | */ 11 | 12 | /** 13 | * @contant {String} Container path 14 | * @see module:containers/CallableContainer 15 | */ 16 | const CONTAINER = p.resolve(__dirname, '../containers/CallableContainer.js') 17 | 18 | /** 19 | * CallableTask interact with the forked script 20 | * @extends module:tasks/ScriptTask~ScriptTask 21 | * @class 22 | * @inheritdoc 23 | */ 24 | function CallableTask(script, options) { 25 | if(!(this instanceof CallableTask)) { return new CallableTask(script, options)} 26 | 27 | if(!options) 28 | options = {} 29 | 30 | options.containers = [CONTAINER].concat(options.containers || []) 31 | 32 | ScriptTask.call(this, script, options) 33 | } 34 | 35 | util.inherits(CallableTask, ScriptTask) 36 | 37 | /** 38 | * Call a script method without expecting an answer 39 | * @param {Mixed} ...arguments 40 | * @return Promise resolve when message has reach destination 41 | */ 42 | CallableTask.prototype.call = function(...args) { 43 | args.unshift('call') 44 | 45 | return new Promise((resolve, reject) => { 46 | args.push(resolve) 47 | this.channel.send.apply(this.channel, args) 48 | }) 49 | } 50 | 51 | /** 52 | * Get a script property, expecting an answer. 53 | * 54 | * The {@link module:containers/CallableContainer Callable Container} is handling functions, promises 55 | * and simple getters 56 | * @param {Mixed} ...arguments 57 | * @return {Promise<...data>} resolves on answer 58 | */ 59 | CallableTask.prototype.get = function(...args) { 60 | let uniqueCallback = uuid.v4() 61 | args.unshift(uniqueCallback) 62 | args.unshift('get') 63 | 64 | return new Promise((resolve, reject) => { 65 | this.channel.once(uniqueCallback, resolve) 66 | this.channel.send.apply(this.channel, args) 67 | }) 68 | } 69 | 70 | module.exports = CallableTask 71 | -------------------------------------------------------------------------------- /packages/relieve-failsafe/src/index.js: -------------------------------------------------------------------------------- 1 | const master = require('./master') 2 | const CONTAINER = `${__dirname}/container.js` 3 | const constants = require('./constants') 4 | const debug = require('debug')('relieve-failsafe:interface') 5 | const fork = require('child_process').fork 6 | 7 | function FailSafe(options) { 8 | if (!(this instanceof FailSafe)) { 9 | return new FailSafe(options) 10 | } 11 | 12 | this.options = constants(options) 13 | } 14 | 15 | FailSafe.prototype.attach = function(task) { 16 | task._createFork = (...args) => { 17 | return this.createFork.apply(this, args) 18 | } 19 | 20 | let oldKill = task.kill 21 | 22 | task.kill = (signal) => { 23 | return new Promise((resolve, reject) => { 24 | if (!task.channel) { 25 | debug('Task %s not running.', task.name) 26 | return resolve() 27 | } 28 | 29 | task.channel.once('exit', () => { 30 | resolve() 31 | }) 32 | 33 | task.channel.send('$TCPEE_KILL', signal === undefined ? 'SIGTERM' : signal) 34 | }) 35 | } 36 | 37 | this.task = task 38 | this.options.eventemitter = task.options.eventemitter 39 | this.task.options.containerArgs.socket = this.options.SOCKET 40 | this.task.options.container = CONTAINER 41 | this.task.options.childprocess.detached = true 42 | } 43 | 44 | FailSafe.prototype.createFork = function(args) { 45 | return master(this.options) 46 | .then(tcpeegroup => { 47 | let tcpee = tcpeegroup.get(this.task.identity) 48 | 49 | if (tcpee === undefined) { 50 | debug('Forking %s %s', this.task.options.container, this.task.script) 51 | this.task._fork = fork(this.task.options.container, args, this.task.options.childprocess) 52 | 53 | return new Promise((resolve, reject) => { 54 | tcpeegroup.once('$TCPEE_ADD:'+this.task.identity, function(tcpee) { 55 | resolve(tcpee) 56 | }) 57 | }) 58 | } 59 | 60 | return new Promise((resolve, reject) => { 61 | tcpee.send('startedAt') 62 | tcpee.once('startedAt', function(startedAt) { 63 | tcpee.startedAt = startedAt 64 | resolve(tcpee) 65 | }) 66 | }) 67 | }) 68 | } 69 | 70 | module.exports = FailSafe 71 | -------------------------------------------------------------------------------- /examples/7-Containers.md: -------------------------------------------------------------------------------- 1 | Containers are surrounding your process, therefore they're really useful if you want to give your processes access to global remote methods. 2 | 3 | A perfect example is to leverage the new `process.cpuUsage`, 'process.memoryUsage' or `process.uptime()`. For this, there is a `MonitorContainer` you can use in any task: 4 | 5 | ```javascript 6 | 7 | var CallableTask = require('relieve/tasks/CallableTask') 8 | var monitorContainer = require.resolve('relieve/containers/MonitorContainer') //gives the module path 9 | 10 | var task = new CallableTask('task.js', { 11 | containers: [ 12 | monitorContainer 13 | ] 14 | }) 15 | 16 | task.start() 17 | .then(function() { 18 | task.once('usage', function(usage) { 19 | console.log(usage) 20 | 21 | /** 22 | * The usage object has the following data: 23 | * 24 | * { 25 | * cpu: process.cpuUsage(), 26 | * cpuPercent: 100, // percent usage computed through hrtime 27 | * memory: process.memoryUsage(), 28 | * pid: process.pid, 29 | * uptime: process.uptime() 30 | * } 31 | * 32 | */ 33 | 34 | }) 35 | 36 | task.send('usage') 37 | }) 38 | 39 | ``` 40 | 41 | Container are scripts that are required before your script task. They can (and should) use the available `process.relieve.ipc` communication channel. 42 | 43 | It's a great way to abstract things you'd need in every tasks. As this is a simple script, that doesn't need any `exports` you can add your own without breaking anything on relieve internals. For example, the `MonitorContainer` code is pretty straightforward: 44 | 45 | ```javascript 46 | // Relieve has not been loaded, load it 47 | if (!process.relieve) { 48 | require('./CallableContainer.js') //can also be 'relieve/containers/CallableContainer' 49 | } 50 | 51 | const os = require('os') 52 | const ipc = process.relieve.ipc 53 | 54 | let cpuUsage 55 | let time 56 | 57 | ipc.on('usage', function() { 58 | cpuUsage = process.cpuUsage(cpuUsage) 59 | time = process.hrtime(time) 60 | 61 | let cpuPercent = (100 * (cpuUsage.system / 1000 + cpuUsage.user / 1000) / (time[0] * 1000 + time[1] / 1e6)) 62 | 63 | ipc.send('usage', { 64 | cpu: cpuUsage, 65 | cpuPercent: cpuPercent, 66 | memory: process.memoryUsage(), 67 | pid: process.pid, 68 | uptime: process.uptime() 69 | }) 70 | }) 71 | ``` 72 | -------------------------------------------------------------------------------- /packages/relieve/workers/QueueWorker.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const util = require('util') 3 | const Worker = require('./Worker.js') 4 | const Promise = require('bluebird') 5 | const debug = require('debug')('relieve:seriesworker') 6 | 7 | /** 8 | * A Queue Worker that process ending tasks in concurrency 9 | * @module workers/QueueWorker 10 | */ 11 | 12 | /** 13 | * @class 14 | * @extends module:workers/Worker~Worker 15 | * @property {Number} [concurrency=1] The concurrency value 16 | */ 17 | function QueueWorker(options) { 18 | if(!(this instanceof QueueWorker)) { return new QueueWorker(options) } 19 | 20 | if(!options) 21 | options = {} 22 | 23 | Worker.call(this, options) 24 | 25 | if(!options.concurrency) 26 | options.concurrency = 1 27 | 28 | this.options = options 29 | } 30 | 31 | util.inherits(QueueWorker, Worker) 32 | 33 | /** 34 | * @throws {TypeError} if task has no start method 35 | */ 36 | QueueWorker.prototype.add = function(task) { 37 | if(!task.start) 38 | throw new TypeError('Task must be an instance of ScriptTask or CallableTask') 39 | 40 | Worker.prototype.add.call(this, task) 41 | return this 42 | } 43 | 44 | Object.defineProperty(QueueWorker.prototype, 'concurrency', { 45 | get: function getConcurrency() { 46 | return this.options.concurrency 47 | }, 48 | set: function setConcurrency(val) { 49 | this.options.concurrency = val 50 | } 51 | }) 52 | 53 | /** 54 | * Runs tasks in the stack 55 | * Tasks must exit to fullfil the promise, they run in concurrency 56 | * @see options.concurrency 57 | * @param Task ...tasks if none provided the whole stack will run 58 | * @return Promise resolves when every task exited 59 | */ 60 | QueueWorker.prototype.run = function(...args) { 61 | let stack = [] 62 | 63 | if(args.length === 0) 64 | stack = this.tasks.values() 65 | else { 66 | for(let i in args) { 67 | stack.push(this.tasks.get(args[i])) 68 | } 69 | } 70 | 71 | return Promise.all(stack).map(function(e) { 72 | return e.start() 73 | .then(() => { 74 | return new Promise((resolve, reject) => { 75 | debug('start task %s', e.name); 76 | e.once('exit', function(code) { 77 | debug('exit task %s', e.name); 78 | resolve(code) 79 | }) 80 | }) 81 | }) 82 | }, { 83 | concurrency: this.options.concurrency 84 | }) 85 | } 86 | 87 | module.exports = QueueWorker 88 | -------------------------------------------------------------------------------- /packages/relieve/test/workers/QueueWorker.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var QueueWorker = require(src + '/workers/QueueWorker.js') 3 | var p = require('path') 4 | var ForkTask = require(src + '/tasks/ForkTask.js') 5 | var fork = require('child_process').fork 6 | 7 | var ScriptTask = require(src + '/tasks/ScriptTask') 8 | 9 | var worker 10 | 11 | describe('QueueWorker', function() { 12 | 13 | it('should fail adding a ForkTask', function() { 14 | var task_fork = fork(p.join(__dirname, '../fixtures/answer.js')) 15 | var task = new ForkTask(task_fork) 16 | 17 | let worker = new QueueWorker() 18 | 19 | try { 20 | worker.add(task) 21 | } catch(e) { 22 | expect(e).to.be.an.instanceof(TypeError) 23 | expect(e.message).to.equal('Task must be an instance of ScriptTask or CallableTask') 24 | task.kill() 25 | } 26 | 27 | }) 28 | 29 | it('should add and remove a task', function() { 30 | let worker = QueueWorker({concurrency: 1}) 31 | let task = ScriptTask(fixtures + '/timeout.js') 32 | task.name = 'test' 33 | worker.add(task) 34 | 35 | worker.on('exit', function() { 36 | }) 37 | worker.once('start', function() { 38 | }) 39 | 40 | return worker.remove('test') 41 | .then(function() { 42 | expect(worker.task('test')).to.be.undefined 43 | expect(worker.tasks.size).to.equal(0) 44 | }) 45 | }) 46 | 47 | it('should add three tasks', function() { 48 | 49 | var tasks = [ 50 | new ScriptTask(fixtures + '/timeout.js'), 51 | new ScriptTask(fixtures + '/timeout.js'), 52 | new ScriptTask(fixtures + '/timeout.js') 53 | ] 54 | 55 | worker = QueueWorker(); 56 | 57 | for(let i in tasks) { 58 | tasks[i].name = 'test'+i 59 | worker.add(tasks[i]) 60 | } 61 | }) 62 | 63 | it('should start one task', function() { 64 | this.timeout(400) 65 | 66 | var promise = worker.run('test1') 67 | 68 | worker.task('test1').once('response', function(t) { 69 | expect(t).to.equal('ok') 70 | }) 71 | 72 | return promise 73 | }) 74 | 75 | it('should start every task in series', function() { 76 | let i = 0 77 | 78 | function timesEvent(t) { 79 | expect(t).to.equal('ok') 80 | i++ 81 | expect(i <= 3).to.be.true 82 | } 83 | 84 | worker.once('response', timesEvent) 85 | 86 | return worker.run() 87 | }) 88 | 89 | it('should start every tasks in concurrency', function() { 90 | worker.concurrency = 3 91 | expect(worker.concurrency).to.equal(3) 92 | return worker.run() 93 | }) 94 | }) 95 | -------------------------------------------------------------------------------- /packages/relieve-failsafe/src/tcpee/server.js: -------------------------------------------------------------------------------- 1 | const Server = require('net').Server 2 | const fs = require('fs') 3 | const existsSync = require('@soyuka/exists-sync') 4 | const TCPEEGroup = require('./group') 5 | const debug = require('debug')('relieve-failsafe:server') 6 | const os = require('os').platform() 7 | const constants = require('../constants') 8 | let connectionsNumber = 0 9 | let timedOut = false 10 | let timeout 11 | let tcpee = null 12 | let server = null 13 | 14 | function close(socketPath) { 15 | return function exit(e) { 16 | try { 17 | fs.unlinkSync(socketPath) 18 | } catch(e) {} 19 | 20 | } 21 | } 22 | 23 | function resetTimeout(time, resolveFn) { 24 | if (timeout) { 25 | clearTimeout(timeout) 26 | } 27 | 28 | timeout = setTimeout(function() { 29 | timedOut = true 30 | resolveFn() 31 | }, time) 32 | } 33 | 34 | function TCPEEServer(options, waitingForNumber = 0) { 35 | if (tcpee) { 36 | if (tcpee._ready === true) { 37 | return Promise.resolve(tcpee) 38 | } 39 | 40 | return new Promise((resolve) => { 41 | tcpee.once('ready', function() { 42 | resolve(tcpee) 43 | }) 44 | }) 45 | } 46 | 47 | debug('Start server, waiting for %s customers', waitingForNumber) 48 | 49 | options = constants(options) 50 | 51 | //this has to block and throw if the socket is open 52 | if (os !== 'win32' && existsSync(options.SOCKET)) { 53 | fs.unlinkSync(options.SOCKET) 54 | } 55 | 56 | process.on('exit', close(options.SOCKET)) 57 | 58 | server = new Server() 59 | server.listen(options.SOCKET) 60 | tcpee = new TCPEEGroup(options) 61 | 62 | return new Promise((resolve, reject) => { 63 | function resolveFn() { 64 | if (timeout) { 65 | clearTimeout(timeout) 66 | } 67 | 68 | tcpee.ready() 69 | resolve(tcpee) 70 | } 71 | 72 | server.on('listening', function() { 73 | server.on('connection', function(sock) { 74 | tcpee.add(sock) 75 | .then(() => { 76 | resetTimeout(options.TIMEOUT, resolveFn) 77 | if (timedOut === false && ++connectionsNumber >= waitingForNumber) { 78 | resolveFn() 79 | } 80 | }) 81 | }) 82 | 83 | if (waitingForNumber === 0) { 84 | resolveFn() 85 | } else { 86 | resetTimeout(options.TIMEOUT, resolveFn) 87 | } 88 | }) 89 | }) 90 | } 91 | 92 | TCPEEServer.close = function() { 93 | tcpee.destroy() 94 | tcpee = null 95 | 96 | return new Promise((resolve, reject) => { 97 | server.close(resolve) 98 | }) 99 | } 100 | 101 | module.exports = TCPEEServer 102 | -------------------------------------------------------------------------------- /packages/relieve/test/tasks/CallableTask.js: -------------------------------------------------------------------------------- 1 | var CallableTask = require(src + '/tasks/CallableTask.js') 2 | var p = require('path') 3 | var task 4 | 5 | describe('CallableTask', function() { 6 | it('should create a new CallableTask', function() { 7 | task = new CallableTask(p.resolve(__dirname, '../fixtures/script.js')) 8 | return task.start() 9 | }) 10 | 11 | it('should create an autorestart callable task', function(cb) { 12 | task = new CallableTask(p.resolve(__dirname, '../fixtures/script.js'), {restart: true}) 13 | 14 | task.once('restart', cb) 15 | 16 | task.start() 17 | .then(() => task.kill()) 18 | }) 19 | 20 | it('should create a new CallableTask without constructor', function() { 21 | task = CallableTask(p.resolve(__dirname, '../fixtures/script.js'), {}) 22 | return task.start() 23 | }) 24 | 25 | it('should call a function', function() { 26 | return task.call('callMe') 27 | }) 28 | 29 | it('should have register an emiting interval', function(cb) { 30 | task.once('working', cb) 31 | }) 32 | 33 | it('should stop the interval', function() { 34 | return task.get('stopMe') 35 | .then(function(response) { 36 | expect(response).to.equal(true) 37 | }) 38 | }) 39 | 40 | it('should get the value', function() { 41 | return task.get('name') 42 | .then(function(response) { 43 | expect(response).to.equal('script') 44 | }) 45 | }) 46 | 47 | it('should get the args', function() { 48 | return task.get('getMe', 'hello') 49 | .then(function(response) { 50 | expect(response).to.equal('hello') 51 | }) 52 | }) 53 | 54 | it('should throw an get error event', function(cb) { 55 | task.call('throw') 56 | 57 | task.once('error', function(err, stack) { 58 | expect(err).not.to.be.undefined 59 | expect(stack).not.to.be.undefined 60 | cb() 61 | }) 62 | }) 63 | 64 | it('should create a new CallableTask with a different Container', function() { 65 | task = new CallableTask(p.resolve(__dirname, '../fixtures/script.js'), {container: p.resolve(src, 'containers/ScriptContainer.js')}) 66 | return task.start() 67 | }) 68 | 69 | it('should kill the task', function() { 70 | return task.kill() 71 | }) 72 | 73 | it('should be monitorable', function(cb) { 74 | let monitorContainer = `${__dirname}/../../containers/MonitorContainer.js` 75 | task = new CallableTask(p.resolve(__dirname, '../fixtures/script.js'), {containers: [monitorContainer]}) 76 | 77 | task.start() 78 | .then(() => { 79 | task.send('usage') 80 | task.once('usage', function(t) { 81 | expect(t).to.have.property('memory') 82 | expect(t).to.have.property('cpu') 83 | cb() 84 | }) 85 | }) 86 | }) 87 | }) 88 | -------------------------------------------------------------------------------- /packages/relieve-logger/index.js: -------------------------------------------------------------------------------- 1 | const Promise = require('bluebird') 2 | const fs = Promise.promisifyAll(require('fs')) 3 | const WritableStream = require('stream').Writable 4 | const rotate = require('./rotate.js') 5 | const moment = require('moment') 6 | 7 | function Logger(out, err, options = {}) { 8 | this.out = out 9 | this.err = err 10 | this.options = { 11 | delay: options.delay || null, 12 | size: options.size || 0 13 | } 14 | } 15 | 16 | Logger.prototype.attach = function(task) { 17 | this.oldStart = task.start 18 | task.start = (...args) => { 19 | return this.start.apply(this, args) 20 | } 21 | 22 | this.task = task 23 | } 24 | 25 | Logger.prototype.start = function(...args) { 26 | return Promise.all([this.getStream(this.out), this.getStream(this.err)]) 27 | .then((streams) => { 28 | this.streams = streams.map(s => { 29 | if (s instanceof WritableStream) { 30 | return s 31 | } 32 | 33 | return null 34 | }) 35 | 36 | this.task.options.childprocess.stdio = [this.streams[0], this.streams[1], null, 'ipc'] 37 | 38 | return this.oldStart.apply(this.task, args) 39 | }) 40 | } 41 | 42 | Logger.prototype.rotate = function(stat) { 43 | let delay = this.options.delay ? this.options.delay.match(/(\d+)([a-z]+)/i) : null 44 | let ctime = moment(stat.ctime) 45 | 46 | if (delay && moment().isAfter(ctime.add(delay[1], delay[2]))) { 47 | return Promise.resolve(true) 48 | } 49 | 50 | if (this.options.size > 0 && stat.size > this.options.size) { 51 | return Promise.resolve(true) 52 | } 53 | 54 | return Promise.resolve(false) 55 | } 56 | 57 | Logger.prototype.getStream = function(something) { 58 | if (!something) { 59 | return null 60 | } 61 | 62 | if (something instanceof WritableStream) { 63 | return Promise.resolve(something) 64 | } 65 | 66 | if (typeof something !== 'string') { 67 | throw new TypeError('Log must be either string or WritableStream') 68 | } 69 | 70 | return fs.statAsync(something) 71 | .then((stat) => { 72 | return this.rotate(stat) 73 | .then((doRotate) => { 74 | return doRotate ? rotate(something) : Promise.resolve() 75 | }) 76 | .then(() => { 77 | return new Promise((resolve) => { 78 | let stream = fs.createWriteStream(something, {'flags': 'a'}) 79 | 80 | stream.on('open', function() { 81 | resolve(stream) 82 | }) 83 | }) 84 | }) 85 | }) 86 | .catch(e => { 87 | if (e.code === 'ENOENT') { 88 | return new Promise((resolve) => { 89 | let stream = fs.createWriteStream(something) 90 | 91 | stream.on('open', function() { 92 | resolve(stream) 93 | }) 94 | }) 95 | } 96 | 97 | return Promise.reject(e) 98 | }) 99 | } 100 | 101 | module.exports = Logger 102 | -------------------------------------------------------------------------------- /packages/relieve-logger/test/index.js: -------------------------------------------------------------------------------- 1 | const Logger = require('../') 2 | const moment = require('moment') 3 | const chai = require('chai') 4 | const expect = chai.expect 5 | const fs = require('fs') 6 | const fakeTask = { 7 | start: function() {}, 8 | options: { 9 | childprocess: {} 10 | } 11 | } 12 | 13 | describe('RelieveLogger', function() { 14 | 15 | it('should create log file and close stream on exit', function(cb) { 16 | let path = `${__dirname}/fixtures/nonexistant` 17 | let l = new Logger(path) 18 | 19 | fakeTask.start = function() { 20 | this.options.childprocess.stdio[0].write('test') 21 | process.nextTick(() => { 22 | this.options.childprocess.stdio[0].end() 23 | }) 24 | 25 | return Promise.resolve() 26 | } 27 | 28 | l.attach(fakeTask) 29 | 30 | fakeTask.start() 31 | .then(() => { 32 | fakeTask.options.childprocess.stdio[0].on('close', function() { 33 | cb() 34 | }) 35 | }) 36 | }) 37 | 38 | it('should append to log file', function(cb) { 39 | let path = `${__dirname}/fixtures/nonexistant` 40 | let l = new Logger(path) 41 | 42 | fakeTask.start = function() { 43 | this.options.childprocess.stdio[0].write('test') 44 | process.nextTick(() => { 45 | this.options.childprocess.stdio[0].end() 46 | }) 47 | 48 | return Promise.resolve() 49 | } 50 | 51 | l.attach(fakeTask) 52 | 53 | fakeTask.start() 54 | .then(() => { 55 | fakeTask.options.childprocess.stdio[0].on('close', function() { 56 | let d = fs.readFileSync(path) 57 | expect(d.toString()).to.equal('testtest') 58 | fs.unlink(path, cb) 59 | }) 60 | }) 61 | }) 62 | 63 | it('should rotate delay', function() { 64 | let l = new Logger('one', 'two', { 65 | delay: '1d' 66 | }) 67 | 68 | let ctime = moment().subtract(2, 'days') 69 | 70 | return l.rotate({ctime: ctime, size: 0}) 71 | .then(rotate => expect(rotate).to.be.true) 72 | }) 73 | 74 | it('should not rotate delay', function() { 75 | let l = new Logger('one', 'two', { 76 | delay: '1d' 77 | }) 78 | 79 | let ctime = moment().subtract(12, 'hours') 80 | 81 | return l.rotate({ctime: ctime, size: 0}) 82 | .then(rotate => expect(rotate).to.be.false) 83 | }) 84 | 85 | it('should rotate size', function() { 86 | let l = new Logger('one', 'two', { 87 | size: 1024 88 | }) 89 | 90 | return l.rotate({ctime: new Date(), size: 2048}) 91 | .then(rotate => expect(rotate).to.be.true) 92 | }) 93 | 94 | it('should not rotate size', function() { 95 | let l = new Logger('one', 'two', { 96 | size: 1024 97 | }) 98 | 99 | return l.rotate({ctime: new Date(), size: 1024}) 100 | .then(rotate => expect(rotate).to.be.false) 101 | }) 102 | }) 103 | -------------------------------------------------------------------------------- /packages/relieve/workers/CloudWorker.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | var util = require('util') 3 | var Worker = require('./Worker.js') 4 | var Promise = require('bluebird') 5 | var debug = require('debug')('relieve:seriesworker') 6 | 7 | const STRATEGY = require('../strategies/WeightedStrategy.js') 8 | 9 | /** 10 | * A Cloud Worker will use the same script and act on each one according to 11 | * the strategy 12 | * @module workers/CloudWorker 13 | */ 14 | 15 | /** 16 | * @class 17 | * @extends module:workers/Worker~Worker 18 | * @property {Strategy} [strategy=Strategy] The round robin strategy 19 | */ 20 | function CloudWorker(options) { 21 | if(!(this instanceof CloudWorker)) { return new CloudWorker(options) } 22 | 23 | if(!options) 24 | options = {} 25 | 26 | Worker.call(this, options) 27 | 28 | this.options = options 29 | this.strategy = options.strategy || STRATEGY 30 | } 31 | 32 | util.inherits(CloudWorker, Worker) 33 | 34 | /** 35 | * @inheritdoc 36 | */ 37 | CloudWorker.prototype.onExit = function(name) { 38 | return function(code) { 39 | this.strategy.remove(name) 40 | return Worker.prototype.onExit.call(this, name)(code) 41 | }.bind(this) 42 | } 43 | 44 | /** 45 | * @throws TypeError if task has no start method 46 | */ 47 | CloudWorker.prototype.add = function(task) { 48 | if(!task.start) 49 | throw new TypeError('Task must be an instance of ScriptTask or CallableTask') 50 | 51 | Worker.prototype.add.call(this, task) 52 | return this 53 | } 54 | 55 | /** 56 | * Send a message to the next available task 57 | * @throws {ReferenceError} 58 | * @param {String} event 59 | * @param {Arguments} ...args 60 | * @return Promise 61 | */ 62 | CloudWorker.prototype.send = function(...args) { 63 | return this.strategy.next() 64 | .then((name) => { 65 | let task = this.tasks.get(name) 66 | return task.send.apply(task, args) 67 | }) 68 | } 69 | 70 | function getOrCallNext(method) { 71 | return function(...args) { 72 | let task 73 | 74 | return this.strategy.next() 75 | .then((name) => { 76 | task = this.task(name) 77 | 78 | if(!(method in task)) 79 | return Promise.reject(new ReferenceError(`The task has no '${method}' method`)) 80 | 81 | return this.strategy.start(name) 82 | }) 83 | .then(() => { 84 | return task[method].apply(task, args) 85 | }) 86 | .then((...newArgs) => { 87 | args = newArgs 88 | return this.strategy.end(task.name) 89 | }) 90 | .then(() => { 91 | return Promise.resolve.apply(Promise, args) 92 | }) 93 | } 94 | } 95 | 96 | /** 97 | * Call `get` on the next strategy available task 98 | * @method 99 | * @see module:tasks/CallableTask~CallableTask#get 100 | */ 101 | CloudWorker.prototype.get = getOrCallNext('get') 102 | 103 | /** 104 | * Call `call` on the next strategy available task 105 | * @method 106 | * @see module:tasks/CallableTask~CallableTask#call 107 | */ 108 | CloudWorker.prototype.call = getOrCallNext('call') 109 | 110 | /** 111 | * Start tasks 112 | * @return Promise resolves when every task is started 113 | */ 114 | CloudWorker.prototype.run = function() { 115 | let stack = [] 116 | for(let task of this.tasks.values()) { 117 | if(task.running === true) 118 | continue; 119 | 120 | let s = task.start() 121 | .then(e => this.strategy.push(task.name)) 122 | 123 | stack.push(s) 124 | } 125 | 126 | return Promise.all(stack) 127 | } 128 | 129 | module.exports = CloudWorker 130 | -------------------------------------------------------------------------------- /examples/8-Interfaces.md: -------------------------------------------------------------------------------- 1 | Interfaces are tools you can use to improve your process management. 2 | 3 | For the following examples, you can use a basic `task.js`: 4 | 5 | ```javascript 6 | // task.js 7 | var ipc = process.relieve.ipc 8 | 9 | module.exports = { 10 | start: function() { 11 | ipc.on('ping', function() { 12 | ipc.send('pong') 13 | }) 14 | 15 | setInterval(e => { 16 | console.log('Still alive') 17 | }, 1000) 18 | } 19 | } 20 | ``` 21 | 22 | ### Logs 23 | 24 | For example, you may want to set up `relieve-logger` to output logs in a file and handle log rotation out of the box: 25 | 26 | ``` 27 | npm install relieve-logger --save #you should have relieve already 28 | ``` 29 | 30 | ```javascript 31 | // worker.js 32 | var Logger = require('relieve-logger') 33 | var ScriptTask = require('relieve/tasks/ScriptTask') 34 | 35 | var logger = new Logger(`logs/out.log`, `logs/err.log`, {delay: '1d'}) // creates a logger that rotates files every day 36 | 37 | var task = new ScriptTask('task.js', { 38 | interfaces: [logger] 39 | }) 40 | ``` 41 | 42 | Yes, that's all you need to do so that `task.js` outputs its logs in `logs/out|err.log` with file rotation! 43 | 44 | ### FailSafe 45 | 46 | Now, with `relieve`, we're in a world where there is a Worker (Master) and one or more Tasks (Child). You may be wondering what happens to your Task if the Worker exits. Well, as we use IPC by default, and that the child process is not detached, the Task will also die. 47 | 48 | This may be a problem, especially if the Task is way more important then the worker. Usually the worker is only there to manage the tasks, and the task should have no trouble working without the worker. 49 | 50 | For this to work, let me introduce the `FailSafe` interface. Instead of using the IPC protocol, it transparently replaces it by a TCP protocol having the same API. 51 | 52 | First let's install the `FailSafe` interface: 53 | 54 | ``` 55 | npm install relieve-failsafe --save 56 | ``` 57 | 58 | To use this, simply add the interface: 59 | 60 | ```javascript 61 | // worker.js 62 | var CallableTask = require('relieve/tasks/ScriptTask') 63 | var FailSafe = require('relieve-failsafe') 64 | 65 | var task = new ScriptTask('task.js', { 66 | interfaces: [new FailSafe()] 67 | }) 68 | 69 | task.start() 70 | .then(() => { 71 | task.on('pong', () => console.log('got pong')) 72 | 73 | task.call('ping') 74 | }) 75 | ``` 76 | 77 | Now launch the `worker.js` script, your task is up. Kill the worker, the task is still up. Restart the worker, it'll re-attach the task. 78 | 79 | > But wait, how do I kill my task now? 80 | 81 | Just use `task.stop()` from the worker: 82 | 83 | ```javascript 84 | task.stop() 85 | ``` 86 | 87 | ### Combine everything 88 | 89 | A more complex example sets up a full-featured task with automatic logging, master-independent and a method to get usage data: 90 | 91 | ```javascript 92 | var CallableTask = require('relieve/tasks/ScriptTask') 93 | var Logger = require('relieve-logger') 94 | var FailSafe = require('relieve-failsafe') 95 | var monitorContainer = require.resolve('relieve/containers/MonitorContainer') 96 | 97 | var logger = new Logger('out.log', 'err.log') 98 | var failSafety = new FailSafe() 99 | 100 | var t = new CallableTask(task.script, { 101 | interfaces: [logger, failSafety], 102 | childprocess: { 103 | env: { 104 | SOME_ENV_VARIABLE: 'foobar' 105 | } 106 | }, 107 | containers: [monitorContainer], 108 | restart: true 109 | }) 110 | ``` 111 | -------------------------------------------------------------------------------- /packages/relieve/test/workers/CloudWorker.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var p = require('path') 3 | var CloudWorker = require(src + '/workers/CloudWorker.js') 4 | var ForkTask = require(src + '/tasks/ForkTask.js') 5 | var fork = require('child_process').fork 6 | var ScriptTask = require(src + '/tasks/ScriptTask.js') 7 | var CallableTask = require(src + '/tasks/CallableTask.js') 8 | var worker 9 | 10 | describe('CloudWorker', function() { 11 | it('should fail adding a ForkTask', function() { 12 | var task_fork = fork(p.join(__dirname, '../fixtures/answer.js')) 13 | var task = new ForkTask(task_fork) 14 | 15 | let worker = new CloudWorker() 16 | 17 | try { 18 | worker.add(task) 19 | } catch(e) { 20 | expect(e).to.be.an.instanceof(TypeError) 21 | expect(e.message).to.equal('Task must be an instance of ScriptTask or CallableTask') 22 | task.kill() 23 | } 24 | 25 | }) 26 | 27 | it('should add and remove a task', function() { 28 | let testworker = CloudWorker({strategy: require(src+'/strategies/WeightedStrategy.js')}) 29 | let task = ScriptTask(fixtures + '/timeout.js') 30 | task.name = 'test' 31 | testworker.add(task) 32 | 33 | //registering events before start 34 | testworker.on('exit', function() { 35 | }) 36 | 37 | testworker.once('start', function() { 38 | }) 39 | 40 | //events will bind to the ipc channel 41 | return testworker.remove('test') 42 | .then(function() { 43 | expect(testworker.task('test')).to.be.undefined 44 | expect(testworker.tasks.size).to.equal(0) 45 | }) 46 | }) 47 | 48 | it('should add and run two tasks', function() { 49 | 50 | let tasks = [ 51 | new CallableTask(fixtures + '/server.js'), 52 | new CallableTask(fixtures + '/server.js') 53 | ] 54 | 55 | worker = CloudWorker(); 56 | 57 | for(let i in tasks) { 58 | tasks[i].name = 'test'+i 59 | worker.add(tasks[i]) 60 | } 61 | 62 | return worker.run() 63 | }) 64 | 65 | it('should add and start a third one', function() { 66 | let task = new CallableTask(fixtures + '/server.js') 67 | task.name = 'test3' 68 | worker.add(task) 69 | 70 | return worker.run() 71 | }) 72 | 73 | it('should receive one answer', function(cb) { 74 | worker.send('ping') 75 | worker.once('pong', cb) 76 | }) 77 | 78 | it('should get one answer', function() { 79 | return worker.get('hello') 80 | .then(function(val) { 81 | expect(val).to.equal('world') 82 | return Promise.resolve() 83 | }) 84 | }) 85 | 86 | it('should call', function(cb) { 87 | worker.call('me') 88 | worker.once('calling', cb) 89 | }) 90 | 91 | it('should exit worker', function(cb) { 92 | worker.kill() 93 | let i = 0; 94 | worker.on('exit', function() { 95 | i++ 96 | if(i === 3) 97 | cb() 98 | }) 99 | }) 100 | 101 | it('should reject the promise because task is not callable', function() { 102 | worker = new CloudWorker() 103 | let task = new ScriptTask(fixtures + '/server.js') 104 | task.name = 'test' 105 | worker.add(task) 106 | 107 | return worker.run() 108 | .then(function() { 109 | return worker.call('test') 110 | .catch(function(e) { 111 | expect(e).to.be.an.instanceof(ReferenceError) 112 | expect(e.message).to.equal("The task has no 'call' method") 113 | 114 | worker.kill() 115 | return Promise.resolve() 116 | }) 117 | }) 118 | }) 119 | 120 | }) 121 | -------------------------------------------------------------------------------- /packages/relieve/workers/Worker.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const readOnly = require('../utils/readOnly.js') 3 | const listenersPropagation = require('../utils/listenersPropagation.js') 4 | const uuid = require('uuid') 5 | const util = require('util') 6 | const EventEmitter = require('eventemitter2').EventEmitter2 7 | const debug = require('debug')('relieve:worker') 8 | 9 | /** 10 | * A basic Worker 11 | * @module workers/Worker 12 | */ 13 | 14 | /** 15 | * Creates a Worker 16 | * @class 17 | * @param {Object} Options 18 | */ 19 | function Worker(options) { 20 | if(!(this instanceof Worker)) { return new Worker(options) } 21 | if(!options) { options = {} } 22 | this._tasks = new Map() 23 | } 24 | 25 | /** 26 | * send a message on every task 27 | * @return {Promise} resolves when every task received the message 28 | */ 29 | Worker.prototype.send = function(...args) { 30 | let stack = [] 31 | 32 | for(let task of this._tasks.values()) { 33 | stack.push(new Promise((resolve, reject) => { 34 | let a = args.slice(0) //clone arguments 35 | a.push(resolve) //adds the resolve callback 36 | task.send.apply(task, a) 37 | })) 38 | } 39 | 40 | return Promise.all(stack) 41 | } 42 | 43 | /** 44 | * Registers an exit listener 45 | * @param {String} name the task name 46 | * @return {Function} The listener that deletes an ended task 47 | */ 48 | Worker.prototype.onExit = function(name) { 49 | const self = this 50 | return function(code) { 51 | debug('Task %s exit with code %d', name, code) 52 | self._tasks.delete(name) 53 | } 54 | } 55 | 56 | /** 57 | * Add a task to the worker 58 | * @method 59 | * @listens Worker.task~event:exit 60 | * @param {Task} task 61 | * @return this 62 | */ 63 | Worker.prototype.add = function(task) { 64 | task.once('exit', this.onExit(task.name)) 65 | 66 | this._tasks.set(task.name, task) 67 | 68 | return this 69 | } 70 | 71 | /** 72 | * Removes a worker by name 73 | * @param {String} name 74 | * @return {Promise} resolves when the task exit event is fired 75 | */ 76 | Worker.prototype.remove = function(name) { 77 | return new Promise((resolve, reject) => { 78 | let task = this.task(name) 79 | 80 | task.once('exit', function() { 81 | resolve() 82 | }) 83 | 84 | process.nextTick(function() { 85 | task.kill() 86 | }) 87 | }) 88 | } 89 | 90 | /** 91 | * Get a task by name 92 | * @param {String} name 93 | * @return {Task} 94 | */ 95 | Worker.prototype.task = function (name) { 96 | return this._tasks.get(name) 97 | } 98 | 99 | /** 100 | * Get the tasks Set 101 | * @return {Set} 102 | */ 103 | 104 | readOnly(Worker, 'tasks', function() { 105 | return this._tasks 106 | }) 107 | 108 | listenersPropagation(Worker, function replicateListener(method) { 109 | return function() { 110 | debug('Register event %s on task', method) 111 | for(let task of this._tasks.values()) { 112 | task[method].apply(task, arguments) 113 | } 114 | } 115 | }) 116 | 117 | /** 118 | * Send a signal to tasks 119 | * @param {Number} Signal 120 | * @see ChildProcess#signal 121 | */ 122 | Worker.prototype.kill = function(signal) { 123 | for(let task of this._tasks.values()) { 124 | task.kill(signal) 125 | } 126 | } 127 | 128 | /** 129 | * Starts every tasks 130 | * @param {Number} Signal 131 | * @see ChildProcess#signal 132 | */ 133 | Worker.prototype.start = function(...args) { 134 | let stack = [] 135 | for(let task of this._tasks.values()) { 136 | stack.push(task.start.apply(task, args)) 137 | } 138 | 139 | return Promise.all(stack) 140 | } 141 | 142 | module.exports = Worker 143 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Relieve [![Test Coverage](https://codeclimate.com/github/soyuka/relieve/badges/coverage.svg)](https://codeclimate.com/github/soyuka/relieve/coverage) [![Code Climate](https://codeclimate.com/github/soyuka/relieve/badges/gpa.svg)](https://codeclimate.com/github/soyuka/relieve) [![Build Status](https://travis-ci.org/soyuka/relieve.svg?branch=master)](https://travis-ci.org/soyuka/relieve) [![Build status](https://ci.appveyor.com/api/projects/status/kixnoqeg0tntgaad?svg=true)](https://ci.appveyor.com/project/soyuka/relieve) 2 | 3 | The goal of this library is to ease the implementation of multi processing accross your existing microservices. 4 | Relieve aims to give a reusable design pattern using process forks. It also eases communication with child processes with an high-level abstraction. 5 | 6 | For example, with a CallableTask: 7 | 8 | ```javascript 9 | //task.js 10 | //just export a module in the child process 11 | module.exports = { 12 | print: (str) => { 13 | console.log(str) 14 | }, 15 | data: () => { 16 | //return some async data 17 | return Promise.resolve({foo: 'bar'}) 18 | } 19 | } 20 | ``` 21 | 22 | Then from your master, just call the task: 23 | 24 | ```javascript 25 | //worker.js 26 | var CallableTask = require('relieve/tasks/CallableTask') 27 | var task = new CallableTask('task.js') 28 | 29 | task.start() 30 | .then(() => { 31 | task.call('print', 'hello world') 32 | return task.get('data') 33 | }) 34 | .then(d => { 35 | //d is {foo: 'bar'} 36 | }) 37 | ``` 38 | 39 | ### The design pattern 40 | 41 | Relieve is based on a design pattern containing: 42 | - A Worker 43 | - One or more tasks 44 | 45 | ![](https://raw.githubusercontent.com/soyuka/relieve/master/examples/images/relieve.jpg) 46 | 47 | The task can be used without a Worker, but the Worker helps managing workflows. 48 | 49 | ### Task 50 | 51 | The task will implement a child process using `fork`. It'll make sure that there is an ipc channel open so that Workers and Tasks can communicate. 52 | There are different tasks implementations: 53 | 54 | - Fork Task - simply transforms a `ChildProcess.fork` in a Task 55 | - Script Task - wraps a script path in a container that is managed through `ChildProcess.fork`. It gives the ability to start, restart or kill a Task 56 | - Callable Task - this is a Script Task with convenience methods to `call` or `get` script methods remotely 57 | 58 | 59 | #### Tutorials: 60 | 61 | - [Fork Task](http://soyuka.github.io/relieve/tutorial-1-ForkTask.html) 62 | - [Script Task](http://soyuka.github.io/relieve/tutorial-2-ScriptTask.html) 63 | - [Callable Task](http://soyuka.github.io/relieve/tutorial-3-CallableTask.html) 64 | 65 | ### Worker 66 | 67 | Different kind of Workers for different use cases. Every Worker takes one or more tasks and handles them. 68 | 69 | - Worker - it's a basic worker. Helps sending a message to every task. 70 | - QueueWorker - process tasks one after the other, or in concurrency. Waits for a Task to exit before it consider's it as done. 71 | - CloudWorker - does not wait for tasks to exit and process them through a Strategy (ie: RoundRobin) 72 | 73 | #### Tutorials: 74 | 75 | - [Worker](http://soyuka.github.io/relieve/tutorial-4-Worker.html) 76 | - [QueueWorker](http://soyuka.github.io/relieve/tutorial-5-QueueWorker.html) 77 | - [CloudWorker](http://soyuka.github.io/relieve/tutorial-6-CloudWorker.html) 78 | 79 | ### Tools 80 | 81 | - [Containers](http://soyuka.github.io/relieve/tutorial-7-Containers.html) - easy way to add ipc methods for your tasks 82 | - [Interfaces](http://soyuka.github.io/relieve/tutorial-8-Interfaces.html) - extends how the tasks are managed (FailSafe, Logger) 83 | 84 | ### Links 85 | - [Documentation](http://soyuka.github.io/relieve/) 86 | - [Coverage](http://soyuka.github.io/relieve/coverage/lcov-report/) 87 | - [Blog post](https://soyuka.me/having-fun-with-nodejs-child-processes/) 88 | -------------------------------------------------------------------------------- /examples/6-CloudWorker.md: -------------------------------------------------------------------------------- 1 | The [CloudWorker]{@link module:workers/CloudWorker~CloudWorker} does not accept ForkTasks. It will handle start/stop action, and therefore when a Task is added it must not be started. 2 | 3 | The CloudWorker differs from the QueueWorker in which it does not wait for tasks to end. It assumes that tasks should run forever, and therefore works well with the `autorestart` feature. 4 | Then, the CloudWorker works with a Strategy, like a Round-Robin that will give the next available task. The default Strategy is a weight strategy that increments/decrements a task-basis counter according to the number of calls the task gets. 5 | 6 | For example: 7 | 8 | ``` 9 | var relieve = require('relieve') 10 | var CallableTask = relieve.tasks.CallableTask 11 | var CloudWorker = relieve.workers.CloudWorker 12 | 13 | var worker = new CloudWorker() 14 | worker.add(new CallableTask('sometask.js')) 15 | worker.add(new CallableTask('sometask.js')) 16 | 17 | worker.run() 18 | //every task has started 19 | .then(function() { 20 | //send ping to the next available task 21 | worker.get('ping') 22 | //if it's a long running task, this second instruction 23 | //will most likely call the second task 24 | worker.get('ping') 25 | }) 26 | ``` 27 | 28 | ### Socket task 29 | 30 | Here we decide to send the Socket to the next available task, to process some data and send him back the requested data. 31 | 32 | The task just handles the fibonnacci calculation, and sends the data directly to the socket. 33 | 34 | ``` 35 | //task.js 36 | 'use strict'; 37 | function fibonacci(max) { 38 | let x = -1; 39 | let i = 0; 40 | let j = 1; 41 | let k = 0; 42 | 43 | for(; k < max; i = j, j = x, k++) { 44 | 45 | if(x > Number.MAX_SAFE_INTEGER) { 46 | console.error('Fibonacci stopeed at iteration %d', k); 47 | return {number: x, iterations: k, error: 'Number exceed the limit ('+Number.MAX_SAFE_INTEGER+')'} 48 | } 49 | 50 | x = i + j 51 | } 52 | 53 | return {number: x, iterations: k} 54 | } 55 | 56 | var socks = [] 57 | var channel = process.relieve.ipc 58 | 59 | module.exports = { 60 | start: function() { 61 | channel.on('socket', function(socket) { 62 | socks.push(socket) 63 | }) 64 | }, 65 | doHeavyStuff: function(num) { 66 | let sock = socks.shift() 67 | let f = fibonacci(num) 68 | if(f.error) { 69 | sock.write('Fibonnacci errored with message: \n') 70 | sock.write(f.error + '\n') 71 | sock.end() 72 | return 73 | } 74 | 75 | sock.write(`Fibonnacci result for ${num} is ${f.number}\n`) 76 | sock.write(`${f.iterations} iterations done\n`) 77 | sock.end() 78 | } 79 | } 80 | ``` 81 | 82 | The worker sends the incoming socket to one of our tasks. 83 | 84 | ``` 85 | 'use strict'; 86 | var relieve = require('relieve') 87 | var CallableTask = relieve.tasks.CallableTask 88 | var Worker = relieve.workers.CloudWorker 89 | var net = require('net') 90 | 91 | const RANDOM_MIN = 1 92 | const RANDOM_MAX = 156 //78 iterations until Number.MAX_SAFE_INTEGER 93 | 94 | var worker = new Worker() 95 | 96 | let i = 0 97 | let len = 4 98 | for (; i < len; i++) { 99 | let task = new CallableTask(__dirname + '/task.js', {restart: true}) 100 | task.name = 'task'+i 101 | worker.add(task) 102 | } 103 | 104 | worker.run() 105 | 106 | var server = net.createServer() 107 | 108 | server.on('connection', function(socket) { 109 | //send the socket to the next available task 110 | worker.send('socket', socket) 111 | .then(function(task) { 112 | let n = Math.floor(Math.random() * (RANDOM_MAX - RANDOM_MIN + 1)) + RANDOM_MIN 113 | //call doHeavyStuff, the task will handle the response 114 | task.call('doHeavyStuff', n) 115 | }) 116 | }) 117 | 118 | server.listen(function() { 119 | console.log('server listening on %j', server.address()); 120 | }) 121 | ``` 122 | 123 | To test this, simply plug a telnet on the server and watch. 124 | -------------------------------------------------------------------------------- /packages/relieve/test/tasks/ScriptTask.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const ScriptTask = require(src + '/tasks/ScriptTask.js') 3 | const p = require('path') 4 | let task 5 | let startedAt 6 | 7 | describe('ScriptTask', function() { 8 | 9 | it('should fail creating a new ScriptTask', function() { 10 | try{ new ScriptTask({foo: 'bar'})} catch(e) { 11 | expect(e).to.be.an.instanceof(TypeError) 12 | expect(e.message).to.equal('Script must be a string!') 13 | } 14 | }) 15 | 16 | it('should create a new ScriptTask', function() { 17 | task = new ScriptTask(p.resolve(__dirname, '../fixtures/script.js')) 18 | return task.start() 19 | .then(() => { 20 | startedAt = task.startedAt 21 | return Promise.resolve() 22 | }) 23 | }) 24 | 25 | it('should reject starting again', function(cb) { 26 | task.start() 27 | .catch(function(e) { 28 | expect(e).to.be.an.instanceof(ReferenceError) 29 | expect(e.message).to.equal('Already running') 30 | cb() 31 | }) 32 | }) 33 | 34 | it('should restart', function() { 35 | return task.restart() 36 | .then(() => { 37 | expect(task.restarts).to.equal(1) 38 | expect(task.running).to.be.true 39 | expect(task.startedAt).not.to.equal(startedAt) 40 | return Promise.resolve() 41 | }) 42 | }) 43 | 44 | it('should reject start', function(cb) { 45 | task.start() 46 | .catch(function(e) { 47 | expect(e).to.be.an.instanceof(ReferenceError) 48 | expect(e.message).to.equal('Already running') 49 | cb() 50 | }) 51 | }) 52 | 53 | it('should kill', function(cb) { 54 | task.kill() 55 | 56 | task.once('exit', function(code) { cb() }) 57 | }) 58 | 59 | it('should create an autorestart task', function(cb) { 60 | task = new ScriptTask(p.resolve(__dirname, '../fixtures/script.js'), {restart: true, restartDelay: 200}) 61 | 62 | task.once('restart', cb) 63 | 64 | task.start() 65 | .then(() => task.kill()) 66 | }) 67 | 68 | it('should register event before starting', function(cb) { 69 | task = new ScriptTask(p.resolve(__dirname, '../fixtures/server.js')) 70 | task.once('started', cb) 71 | task.start() 72 | }) 73 | 74 | it('should send a message to the task and resolve promise when message has been delivered', function() { 75 | return task.send('message', 'hello', 'world') 76 | .then(function(t) { 77 | expect(t).to.deep.equal(task) 78 | return Promise.resolve() 79 | }) 80 | }) 81 | 82 | it('should fail sending a message if the task is not started', function(cb) { 83 | let task = new ScriptTask(p.resolve(__dirname, '../fixtures/script.js')) 84 | 85 | try { 86 | task.send('message', 'hello', 'world') 87 | } catch(e) { 88 | expect(e).to.be.an.instanceof(ReferenceError) 89 | expect(e.message).to.equal('The task is not running') 90 | cb() 91 | } 92 | }) 93 | 94 | it('should start a task with arguments', function(cb) { 95 | let task = new ScriptTask(p.resolve(__dirname, '../fixtures/arguments.js')) 96 | 97 | task.start('Hello World') 98 | 99 | task.once('arguments', function(args) { 100 | expect(args[1]).to.equal('Hello World') 101 | 102 | task.kill() 103 | 104 | process.nextTick(cb) 105 | }) 106 | }) 107 | 108 | it('should start a task with complex arguments', function(cb) { 109 | let task = new ScriptTask(p.resolve(__dirname, '../fixtures/arguments.js')) 110 | 111 | task.arguments = [{src: 'This', dest: 'That'}, ['foo', 'bar']] 112 | task.start() 113 | 114 | task.once('arguments', function(args) { 115 | expect(args[1]).to.deep.equal({src: 'This', dest: 'That'}) 116 | expect(args[2]).to.deep.equal(['foo', 'bar']) 117 | 118 | task.kill() 119 | 120 | process.nextTick(cb) 121 | }) 122 | }) 123 | 124 | it('should add one interface', function() { 125 | function Logger() { 126 | } 127 | 128 | Logger.prototype.attach = (task) => { 129 | task.start = 'attached' 130 | } 131 | 132 | let task = new ScriptTask(`${__dirname}/../fixtures/script.js`, {interfaces: [new Logger]}) 133 | 134 | expect(task.start).to.equal('attached') 135 | }) 136 | 137 | it('should not add invalid interface', function() { 138 | function Logger() { 139 | } 140 | 141 | Logger.prototype.invalidattach = (task) => { 142 | task.start = 'attached' 143 | } 144 | 145 | let task = new ScriptTask(`${__dirname}/../fixtures/script.js`, {interfaces: [new Logger]}) 146 | 147 | expect(task.start).not.to.equal('attached') 148 | }) 149 | 150 | it('should stop and not restart', function(cb) { 151 | task = new ScriptTask(p.resolve(__dirname, '../fixtures/script.js'), {restart: true, restartDelay: 200}) 152 | 153 | task.start() 154 | .then(() => task.stop()) 155 | .then(() => { 156 | expect(task.restarts).to.equal(0) 157 | expect(task.running).to.be.false 158 | cb() 159 | }) 160 | }) 161 | 162 | it('should still be an autorestart task', function(cb) { 163 | task.restart = cb 164 | 165 | task.start() 166 | .then(() => task.kill()) 167 | }) 168 | }) 169 | -------------------------------------------------------------------------------- /packages/relieve-failsafe/test/end2end.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const ScriptTask = require('relieve/tasks/ScriptTask') 3 | const Worker = require('relieve/workers/Worker') 4 | const CallableTask = require('relieve/tasks/CallableTask') 5 | const FailSafe = require('../src/index.js') 6 | const relieveFixturesPath = `${__dirname}/../node_modules/relieve/test/fixtures` 7 | const expect = require('chai').expect 8 | const existsSync = require('@soyuka/exists-sync') 9 | const fs = require('fs') 10 | 11 | const os = require('os').platform() 12 | const SOCKET = os === 'win32' ? `\\\\?\\pipe\\${__dirname}\\e2e.sock`: `./e2e.sock` 13 | 14 | let failSafety = new FailSafe({SOCKET: SOCKET, PERSISTENCE: './relieve.count'}) 15 | let task 16 | let startedAt 17 | 18 | describe('e2e ScriptTask', function() { 19 | before(function() { 20 | if (existsSync('./relieve.count')) { 21 | fs.unlinkSync('./relieve.count') 22 | } 23 | }) 24 | 25 | after(function() { 26 | if (existsSync('./relieve.count')) { 27 | fs.unlinkSync('./relieve.count') 28 | } 29 | }) 30 | 31 | it('should start a Worker', function() { 32 | const worker = new Worker() 33 | 34 | let task = new CallableTask(`${relieveFixturesPath}/arguments.js`, { 35 | interfaces: [new FailSafe({SOCKET: SOCKET, PERSISTENCE: './relieve.count'})] 36 | }) 37 | task.name = 'foo' 38 | 39 | worker.add(task) 40 | 41 | let task2 = new CallableTask(`${relieveFixturesPath}/arguments.js`, { 42 | interfaces: [new FailSafe({SOCKET: SOCKET, PERSISTENCE: './relieve.count'})] 43 | }) 44 | task2.name = 'bar' 45 | 46 | worker.add(task2) 47 | 48 | return worker.start() 49 | .then(e => { 50 | expect(worker.tasks.size).to.equal(2) 51 | 52 | return worker.remove('foo') 53 | }) 54 | .then(e => { 55 | expect(worker.tasks.size).to.equal(1) 56 | expect(worker.task('foo')).to.be.undefined 57 | 58 | return worker.remove('bar') 59 | }) 60 | }) 61 | 62 | it('should create a new ScriptTask', function() { 63 | task = new ScriptTask(`${relieveFixturesPath}/script.js`, { 64 | interfaces: [failSafety] 65 | }) 66 | 67 | return task.start() 68 | .then(() => { 69 | startedAt = task.startedAt 70 | return Promise.resolve() 71 | }) 72 | }) 73 | 74 | it('should reject starting again', function(cb) { 75 | task.start() 76 | .catch(function(e) { 77 | expect(e).to.be.an.instanceof(ReferenceError) 78 | expect(e.message).to.equal('Already running') 79 | cb() 80 | }) 81 | }) 82 | 83 | it('should restart', function() { 84 | return task.restart() 85 | .then(() => { 86 | expect(task.restarts).to.equal(1) 87 | expect(task.running).to.be.true 88 | expect(task.startedAt).not.to.equal(startedAt) 89 | return Promise.resolve() 90 | }) 91 | }) 92 | 93 | it('should reject start', function(cb) { 94 | task.start() 95 | .catch(function(e) { 96 | expect(e).to.be.an.instanceof(ReferenceError) 97 | expect(e.message).to.equal('Already running') 98 | cb() 99 | }) 100 | }) 101 | 102 | it('should kill', function(cb) { 103 | task.kill() 104 | 105 | task.once('exit', function(code) { cb() }) 106 | }) 107 | 108 | it('should create an autorestart task', function(cb) { 109 | task = new ScriptTask(`${relieveFixturesPath}/script.js`, { 110 | interfaces: [failSafety], 111 | restart: true 112 | }) 113 | 114 | task.once('restart', cb) 115 | 116 | task.start() 117 | .then(() => task.kill()) 118 | }) 119 | 120 | it('should stop and not restart', function(cb) { 121 | setTimeout(() => { 122 | task.once('exit', () => cb()) 123 | task.once('restart', () => cb()) 124 | 125 | task.stop() 126 | .then(() => {}) 127 | }, 200) 128 | }) 129 | 130 | it('should register event before starting', function(cb) { 131 | task = new ScriptTask(`${relieveFixturesPath}/server.js`, { 132 | interfaces: [failSafety] 133 | }) 134 | 135 | task.once('started', cb) 136 | task.start() 137 | }) 138 | 139 | it('should send a message to the task and resolve promise when message has been delivered', function() { 140 | return task.send('message', 'hello', 'world') 141 | .then(function(t) { 142 | expect(t).to.deep.equal(task) 143 | return task.stop() 144 | }) 145 | }) 146 | 147 | it('should fail sending a message if the task is not started', function(cb) { 148 | let task = new ScriptTask(`${relieveFixturesPath}/script.js`, { 149 | interfaces: [failSafety] 150 | }) 151 | 152 | try { 153 | task.send('message', 'hello', 'world') 154 | } catch(e) { 155 | expect(e).to.be.an.instanceof(ReferenceError) 156 | expect(e.message).to.equal('The task is not running') 157 | cb() 158 | } 159 | }) 160 | 161 | it('should start a task with arguments', function(cb) { 162 | let task = new ScriptTask(`${relieveFixturesPath}/arguments.js`, { 163 | interfaces: [failSafety] 164 | }) 165 | 166 | task.once('arguments', function(args) { 167 | expect(args[1]).to.equal('Hello World') 168 | 169 | task.stop() 170 | .then(e => { 171 | cb() 172 | }) 173 | }) 174 | 175 | task.start('Hello World') 176 | }) 177 | 178 | it('should start a task with complex arguments', function(cb) { 179 | let task = new ScriptTask(`${relieveFixturesPath}/arguments.js`, { 180 | interfaces: [failSafety] 181 | }) 182 | 183 | task.once('arguments', function(args) { 184 | expect(args[1]).to.deep.equal({src: 'This', dest: 'That'}) 185 | expect(args[2]).to.deep.equal(['foo', 'bar']) 186 | 187 | task.stop() 188 | .then(cb) 189 | }) 190 | 191 | task.arguments = [{src: 'This', dest: 'That'}, ['foo', 'bar']] 192 | task.start() 193 | }) 194 | 195 | it('should start a callable task', function() { 196 | let task = new CallableTask(`${relieveFixturesPath}/script.js`, { 197 | interfaces: [failSafety] 198 | }) 199 | 200 | return task.start() 201 | .then(e => { 202 | return task.get('getMe', {foo: 'bar'}) 203 | .then(e => { 204 | expect(e).to.deep.equal({foo: 'bar'}) 205 | 206 | return task.stop() 207 | }) 208 | }) 209 | }) 210 | }) 211 | -------------------------------------------------------------------------------- /examples/5-QueueWorker.md: -------------------------------------------------------------------------------- 1 | The [QueueWorker]{@link module:workers/QueueWorker~QueueWorker} does not accept ForkTasks. It will handle start/stop action, and therefore when a Task is added it must not be started. 2 | 3 | The QueueWorker expect tasks to exit once their job is done, it'll then start queued tasks according to the concurrency setting. 4 | If you want to run things in series or in parallel, you'll have to set the according concurrency. For example, having a concurrency to 1 will run each task when the previous one exits. On the other hand, setting concurrency to 10 will run at most 10 tasks at once. 5 | 6 | For example: 7 | 8 | ``` 9 | var relieve = require('relieve') 10 | var ScriptTask = relieve.tasks.ScriptTask 11 | var QueueWorker = relieve.workers.QueueWorker 12 | 13 | var worker = new QueueWorker({concurrency: 10}) 14 | 15 | worker 16 | .add(new ScriptTask('sometask.js')) 17 | .add(new ScriptTask('sometask.js')) 18 | 19 | worker.run() 20 | //each task has exit, we can remove them from the worker 21 | .then(function() { 22 | for(let t of worker.tasks) { 23 | worker.remove(t.name) 24 | } 25 | }) 26 | ``` 27 | 28 | You can find working use cases [here](./usecases). 29 | 30 | ### Compressing Task 31 | 32 | ```javascript 33 | //task.js 34 | 'use strict'; 35 | var archiver = require('archiver') 36 | var fs = require('fs') 37 | var channel = process.relieve.ipc 38 | 39 | module.exports = { 40 | start: function() { 41 | var archive = archiver('zip') 42 | var action = process.argv[3] 43 | var output = fs.createWriteStream(action.dest) 44 | 45 | output.on('close', function() { 46 | channel.send('finish', archive.pointer() + ' bytes written') 47 | process.exit(0) //Note that we exit this tasks when it's done 48 | }) 49 | 50 | archive.on('error', function(err) { throw err }) 51 | archive.pipe(output) 52 | archive.bulk([action.src]) 53 | archive.finalize() 54 | } 55 | } 56 | ``` 57 | 58 | This task, when started, will compress things we gave through the task arguments. The QueueWorker will be able to run lots of those in separated processes. 59 | 60 | ```javascript 61 | 'use strict'; 62 | var relieve = require('relieve') 63 | var ScriptTask = relieve.tasks.ScriptTask 64 | var QueueWorker = relieve.workers.QueueWorker 65 | 66 | var worker = new QueueWorker({concurrency: 10}) 67 | 68 | //assuming that I have a request for 5 compressions 69 | var actions = [ 70 | {src: ['fixtures/somepath', 'fixtures/somedir/'], dest: 'one.zip'}, 71 | {src: ['fixtures/foo'], dest: 'two.zip'}, 72 | {src: ['fixtures/bar'], dest: 'three.zip'}, 73 | {src: ['fixtures/video.mkv', 'fixtures/audio.mp3'], dest: 'four.zip'}, 74 | {src: ['fixtures/directory/*'], dest: 'five.zip'}, 75 | ] 76 | 77 | //I'm creating one task per action 78 | for(let i in actions) { 79 | var task = new ScriptTask(__dirname + '/task.js') 80 | task.name = actions[i].dest //the name is the destination name 81 | task.arguments = [actions[i]] //give my action as arguments to the task 82 | 83 | worker.add(task) 84 | } 85 | 86 | //compress everything 87 | worker.run() 88 | //remove tasks when done 89 | .then(function() { 90 | for(let t of worker.tasks) { 91 | worker.remove(t.name) 92 | } 93 | }) 94 | 95 | ``` 96 | 97 | ### Cksfv 98 | 99 | `cksfv` is a tool that compares CRC hashes. It is commonly used to check the integrity of a multi-part rar file. It works by calculating the hash of each file and comparing the hash to the expected one. 100 | This example shows how the QueueWorker can be used to share a cpu-intensive task through multiple process. My benchmarks shows an improvement with many large parts, it won't show improvements with small parts (ie < 100mb). 101 | 102 | For example, using 50 250mb parts, having a 20 concurrency: 103 | 104 | - without workers 832125ms (13.8 min), cpu use is about 32% on one process 105 | - without workers 225141ms (3.75 min), cpu use is about 2% on each of the 20 child processes 106 | 107 | The Task: 108 | 109 | ``` 110 | //task.js 111 | 'use strict'; 112 | var channel = process.relieve.ipc 113 | var readLine = require('./readLine.js') 114 | 115 | module.exports = { 116 | readLine: function(path, line) { 117 | readLine(path, line) 118 | .then(function(resp) { 119 | channel.send('cksfv', resp) 120 | process.exit(0) 121 | }) 122 | } 123 | } 124 | ``` 125 | 126 | This is the part that calculates the CRC hash: 127 | 128 | ``` 129 | //readLine.js 130 | 'use strict'; 131 | var Promise = require('bluebird') 132 | var crc = require('crc') 133 | var fs = Promise.promisifyAll(require('fs')) 134 | var p = require('path') 135 | 136 | function readLine(path, line) { 137 | if(!line.trim()) 138 | return Promise.resolve() 139 | 140 | var original = line.trim().slice(-8) 141 | var filepath = line.slice(0, -9).trim() 142 | var resp = {original: original, filepath: filepath} 143 | 144 | console.log('Processsing %s', p.resolve(p.dirname(path), filepath)) 145 | 146 | return fs.readFileAsync(p.resolve(p.dirname(path), filepath)) 147 | .then(function(buffer) { 148 | var str = crc.crc32(buffer).toString(16) 149 | 150 | while(str.length < 8) { 151 | str = '0'+str 152 | } 153 | 154 | resp.calculate = str 155 | 156 | return resp 157 | }) 158 | } 159 | 160 | module.exports = readLine 161 | ``` 162 | 163 | The Worker reads the `.sfv` file and sends every line to a new Task. 164 | 165 | It's then executed with: `node worker.js path/to/file.sfv`. 166 | 167 | ``` 168 | //worker.js 169 | 'use strict'; 170 | var relieve = require('relieve') 171 | var assert = require('assert') 172 | var Promise = require('bluebird') 173 | var p = require('path') 174 | var fs = Promise.promisifyAll(require('fs')) 175 | var eol = require('os').EOL 176 | var CallableTask = relieve.tasks.CallableTask 177 | var QueueWorker = relieve.workers.QueueWorker 178 | 179 | assert.ok( 180 | typeof process.argv[2] == 'string' && p.extname(process.argv[2]) == '.sfv', 181 | 'Sfv file must be provided' 182 | ); 183 | 184 | var path = process.argv[2] 185 | var map = [] 186 | 187 | //Call my task method 188 | function call(line) { 189 | return function() { 190 | worker.task(line).call('readLine', path, line) 191 | } 192 | } 193 | 194 | var worker = new QueueWorker({concurrency: 20}) 195 | 196 | console.time('cksfv') 197 | 198 | fs.readFileAsync(path) 199 | .then(function(data) { 200 | data = data.toString() 201 | .split(eol) 202 | .map(e => e.trim()) 203 | .filter(e => e.length) 204 | 205 | //Creates tasks for each lines, those are not started yet 206 | data.map(function(line) { 207 | let task = new CallableTask(__dirname + '/task.js') 208 | task.name = line 209 | task.once('start', call(line)) 210 | task.once('cksfv', function(resp) { 211 | map.push(resp) 212 | }) 213 | worker.add(task) 214 | }) 215 | 216 | //Here we start tasks keeping a 20 concurrency 217 | worker.run() 218 | .then(function() { 219 | let errors = 0 220 | for(let i in map) { 221 | if(map[i].original != map[i].calculate) { 222 | console.error('File %s does not match crc', map[i].filepath) 223 | errors++ 224 | } 225 | } 226 | 227 | console.log('Done checking with %d errors', errors) 228 | console.timeEnd('cksfv') 229 | }) 230 | }) 231 | ``` 232 | -------------------------------------------------------------------------------- /packages/relieve/tasks/ScriptTask.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const fork = require('child_process').fork 3 | const Promise = require('bluebird') 4 | const IPCEE = require('ipcee') 5 | const p = require('path') 6 | const util = require('util') 7 | const defineNameProperty = require('../utils/defineNameProperty.js') 8 | const listenersPropagation = require('../utils/listenersPropagation.js') 9 | const debug = require('debug')('relieve:scripttask') 10 | const EventEmitter = require('eventemitter2').EventEmitter2 11 | 12 | /** 13 | * A Script Task 14 | * @module tasks/ScriptTask 15 | */ 16 | 17 | /** 18 | * @contant {String} Container path 19 | * @see module:containers/ScriptContainer 20 | */ 21 | const CONTAINER = p.resolve(__dirname, '../containers/ScriptContainer.js') 22 | 23 | /** 24 | * ScriptTask takes a module path and runs it in a container 25 | * @class 26 | * @extends IPCEE 27 | * @see {@link https://github.com/soyuka/IPCEE} 28 | * @see {@link https://nodejs.org/api/child_process.html} 29 | * @param {String} script the path of the script 30 | * @param {Object} options 31 | * @property {ChildProcess} channel the ipcee channel 32 | * @property {String} [name=uuid.v4] the task name 33 | * @property {Object} options Task options 34 | * @property {Boolean} options.restart Restart when exit 35 | * @property {Number} options.restartDelay Delay before restart 36 | * @property {String} options.container Base Container 37 | * @property {Array} options.containers An array of additional containers 38 | * @property {Object} options.eventemitter Eventemitter2 options 39 | * @property {Object} options.childprocess childprocess.Fork options 40 | * @property {Object} options.interfaces Javascript prototype which grasp on the Task prototye 41 | * @property {[]} running the ipcee channel 42 | */ 43 | function ScriptTask(script, options) { 44 | 45 | if(!(this instanceof ScriptTask)) { return new ScriptTask(script, options) } 46 | 47 | if(!options) 48 | options = {} 49 | 50 | if(typeof script !== 'string') 51 | throw new TypeError('Script must be a string!') 52 | 53 | this.script = script 54 | 55 | this.options = { 56 | restart: options.restart || false, 57 | restartDelay: options.restartDelay || 0, 58 | containers: options.containers || [], 59 | containerArgs: {}, 60 | container: options.container || CONTAINER, 61 | eventemitter: options.eventemitter || {wildcard: false}, 62 | childprocess: options.childprocess || {}, 63 | interfaces: options.interfaces || [] 64 | } 65 | 66 | this.running = false 67 | this.events = [] 68 | this.arguments = [] 69 | this.startedAt = 0 70 | this.restarts = -1 71 | this._nameGenerated = true 72 | 73 | this.options.interfaces.length && this.options.interfaces.map(i => { 74 | if (typeof i.attach !== 'function') { 75 | console.error('Interface should have an attach method') 76 | return 77 | } 78 | 79 | i.attach(this) 80 | }) 81 | } 82 | 83 | defineNameProperty(ScriptTask) 84 | 85 | /** 86 | * @event module:tasks/ScriptTask~ScriptTask#exit 87 | * @property {Number} code 88 | */ 89 | 90 | /** 91 | * Exit event listener 92 | * @param {Number} code the exit code 93 | * @listens module:tasks/ScriptTask~ScriptTask#exit 94 | */ 95 | ScriptTask.prototype.onExit = function(code) { 96 | this.running = false 97 | this.events = [] 98 | 99 | if(this.shouldRestart === false) { 100 | return 101 | } 102 | 103 | this.restart() 104 | } 105 | 106 | /** 107 | * Error event listener 108 | * @param {String} error message 109 | * @param {String} stack stack trace 110 | * @listens module:tasks/ScriptTask~ScriptTask#error 111 | */ 112 | ScriptTask.prototype.onError = function(message, stack) { 113 | console.error('Error caught on %s', this.name) 114 | console.error(stack) 115 | } 116 | 117 | ScriptTask.prototype._createFork = function(args) { 118 | debug('Forking %s with args %o and options %j', this.script, args) 119 | this._fork = fork(this.options.container, args, this.options.childprocess) 120 | return Promise.resolve(new IPCEE(this._fork, this.options.eventemitter)) 121 | } 122 | 123 | /** 124 | * Start the Task 125 | * @param {Arguments} args Arguments for the child_process 126 | * @throws {ReferenceError} if already running 127 | * @listens exit 128 | * @return {Promise} resolve when Task emits start 129 | */ 130 | ScriptTask.prototype.start = function(...args) { 131 | if(this.running === true) { 132 | return Promise.reject(new ReferenceError('Already running')) 133 | } 134 | 135 | this.shouldRestart = this.options.restart 136 | 137 | if(args.length === 0) 138 | args = this.arguments 139 | 140 | args = args.map(e => JSON.stringify(e)) 141 | 142 | //adds the script in first position 143 | args.unshift(this.script) 144 | //adds the event emitter options in last position 145 | let containerArgs = this.options.containerArgs 146 | containerArgs.eventemitter = this.options.eventemitter 147 | containerArgs.containers = this.options.containers 148 | this.identity = containerArgs.identity = this._nameGenerated === false ? this.name : this.script 149 | 150 | args.push(JSON.stringify(containerArgs)) 151 | 152 | return this._createFork(args) 153 | .then((channel) => { 154 | return new Promise((resolve, reject) => { 155 | this.channel = channel 156 | this.channel.on('error', this.onError.bind(this)) 157 | this.channel.once('exit', this.onExit.bind(this)) 158 | 159 | for(let i in this.events) { 160 | let e = this.events[i] 161 | debug('Registering event', e) 162 | this.channel[e.method].apply(this.channel, e.args) 163 | } 164 | 165 | this.running = true 166 | 167 | this.channel.send('$RELIEVE_REQUIRE') 168 | 169 | if (channel.startedAt !== undefined) { 170 | this.startedAt = channel.startedAt 171 | this.restarts++ 172 | return resolve() 173 | } 174 | 175 | this.channel.once('start', (startedAt) => { 176 | this.startedAt = startedAt 177 | this.restarts++ 178 | resolve() 179 | }) 180 | }) 181 | }) 182 | } 183 | 184 | /** 185 | * Restart the Task 186 | * @return {Promise} resolves when restarted 187 | */ 188 | ScriptTask.prototype.restart = function() { 189 | let restart = () => { 190 | this.channel.emit('restart') 191 | 192 | return Promise.delay(this.options.restartDelay).then(() => { 193 | this.shouldRestart = true 194 | return this.start() 195 | }) 196 | } 197 | 198 | if(this.running === true) { 199 | return this.stop() 200 | .then(restart) 201 | } else { 202 | return restart() 203 | } 204 | } 205 | 206 | /** 207 | * Fake event emitter, call events left in the stack 208 | * @param {String} event 209 | * @param {Array} args 210 | * @private 211 | */ 212 | ScriptTask.prototype._fakeEmit = function(event, args) { 213 | let remove = [] 214 | 215 | for(let i in this.events) { 216 | let e = this.events[i] 217 | 218 | if(e.args[0] == event) { 219 | e.args[1].apply(this, args) 220 | 221 | if(~['once'].indexOf(e.method)) { 222 | remove.push(i) 223 | } 224 | } 225 | } 226 | 227 | for(let i in remove) 228 | this.events.splice(remove[i], 1) 229 | } 230 | 231 | ScriptTask.prototype.stop = function() { 232 | if (this.shouldRestart) { 233 | this.shouldRestart = false 234 | } 235 | 236 | return this.kill() 237 | } 238 | 239 | /** 240 | * Kill sends a signal to the Task 241 | * @param {Number} signal 242 | * @see ChildProcess#signal 243 | */ 244 | ScriptTask.prototype.kill = function(signal) { 245 | if(!this.channel) { 246 | return this._fakeEmit('exit', [-1]) 247 | } 248 | 249 | let client = this.channel.client 250 | 251 | return new Promise((resolve, reject) => { 252 | this.channel.once('exit', () => resolve()) 253 | 254 | client.kill.call(client, signal) 255 | }) 256 | } 257 | 258 | /** 259 | * Childprocess.send wrapper 260 | * @throws {ReferenceError} 261 | * @param {String} event 262 | * @param {Arguments} ...args 263 | * @return Promise 264 | */ 265 | ScriptTask.prototype.send = function(/** event, args **/) { 266 | if(!this.channel) { 267 | throw new ReferenceError('The task is not running') 268 | } 269 | 270 | let args = [].slice.call(arguments) 271 | let channel = this.channel 272 | 273 | return new Promise((resolve, reject) => { 274 | args.push(() => { 275 | return resolve(this) 276 | }) 277 | 278 | channel.send.apply(channel, args) 279 | }) 280 | } 281 | 282 | /** 283 | * Mimic the IPCEE methods, register events on every task 284 | */ 285 | listenersPropagation(ScriptTask, function replicateListener(method) { 286 | return function() { 287 | let args = [].slice.call(arguments) 288 | 289 | if(!this.channel) { 290 | debug('No channel, keep event', {method: method, args: args}) 291 | this.events.push({method: method, args: args}) 292 | } else { 293 | this.channel[method].apply(this.channel, args) 294 | } 295 | } 296 | }) 297 | 298 | module.exports = ScriptTask 299 | -------------------------------------------------------------------------------- /packages/relieve-logger/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "relieve-logger", 3 | "version": "2.2.2", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@soyuka/exists": { 8 | "version": "1.0.1", 9 | "resolved": "https://registry.npmjs.org/@soyuka/exists/-/exists-1.0.1.tgz", 10 | "integrity": "sha1-wL+vCXKC7hkQsmZwEeT6UV8PAyQ=" 11 | }, 12 | "assertion-error": { 13 | "version": "1.1.0", 14 | "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", 15 | "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", 16 | "dev": true 17 | }, 18 | "balanced-match": { 19 | "version": "1.0.0", 20 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 21 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 22 | "dev": true 23 | }, 24 | "bluebird": { 25 | "version": "3.5.1", 26 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", 27 | "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" 28 | }, 29 | "brace-expansion": { 30 | "version": "1.1.11", 31 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 32 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 33 | "dev": true, 34 | "requires": { 35 | "balanced-match": "^1.0.0", 36 | "concat-map": "0.0.1" 37 | } 38 | }, 39 | "browser-stdout": { 40 | "version": "1.3.1", 41 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", 42 | "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", 43 | "dev": true 44 | }, 45 | "chai": { 46 | "version": "3.5.0", 47 | "resolved": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz", 48 | "integrity": "sha1-TQJjewZ/6Vi9v906QOxW/vc3Mkc=", 49 | "dev": true, 50 | "requires": { 51 | "assertion-error": "^1.0.1", 52 | "deep-eql": "^0.1.3", 53 | "type-detect": "^1.0.0" 54 | } 55 | }, 56 | "commander": { 57 | "version": "2.15.1", 58 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", 59 | "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", 60 | "dev": true 61 | }, 62 | "concat-map": { 63 | "version": "0.0.1", 64 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 65 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 66 | "dev": true 67 | }, 68 | "debug": { 69 | "version": "3.1.0", 70 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 71 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 72 | "dev": true, 73 | "requires": { 74 | "ms": "2.0.0" 75 | } 76 | }, 77 | "deep-eql": { 78 | "version": "0.1.3", 79 | "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", 80 | "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", 81 | "dev": true, 82 | "requires": { 83 | "type-detect": "0.1.1" 84 | }, 85 | "dependencies": { 86 | "type-detect": { 87 | "version": "0.1.1", 88 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", 89 | "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", 90 | "dev": true 91 | } 92 | } 93 | }, 94 | "diff": { 95 | "version": "3.5.0", 96 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", 97 | "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", 98 | "dev": true 99 | }, 100 | "escape-string-regexp": { 101 | "version": "1.0.5", 102 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 103 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 104 | "dev": true 105 | }, 106 | "fs.realpath": { 107 | "version": "1.0.0", 108 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 109 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 110 | "dev": true 111 | }, 112 | "glob": { 113 | "version": "7.1.2", 114 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", 115 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", 116 | "dev": true, 117 | "requires": { 118 | "fs.realpath": "^1.0.0", 119 | "inflight": "^1.0.4", 120 | "inherits": "2", 121 | "minimatch": "^3.0.4", 122 | "once": "^1.3.0", 123 | "path-is-absolute": "^1.0.0" 124 | } 125 | }, 126 | "growl": { 127 | "version": "1.10.5", 128 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", 129 | "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", 130 | "dev": true 131 | }, 132 | "has-flag": { 133 | "version": "3.0.0", 134 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 135 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 136 | "dev": true 137 | }, 138 | "he": { 139 | "version": "1.1.1", 140 | "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", 141 | "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", 142 | "dev": true 143 | }, 144 | "inflight": { 145 | "version": "1.0.6", 146 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 147 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 148 | "dev": true, 149 | "requires": { 150 | "once": "^1.3.0", 151 | "wrappy": "1" 152 | } 153 | }, 154 | "inherits": { 155 | "version": "2.0.3", 156 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 157 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 158 | "dev": true 159 | }, 160 | "minimatch": { 161 | "version": "3.0.4", 162 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 163 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 164 | "dev": true, 165 | "requires": { 166 | "brace-expansion": "^1.1.7" 167 | } 168 | }, 169 | "minimist": { 170 | "version": "0.0.8", 171 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 172 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", 173 | "dev": true 174 | }, 175 | "mkdirp": { 176 | "version": "0.5.1", 177 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 178 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 179 | "dev": true, 180 | "requires": { 181 | "minimist": "0.0.8" 182 | } 183 | }, 184 | "mocha": { 185 | "version": "5.2.0", 186 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", 187 | "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", 188 | "dev": true, 189 | "requires": { 190 | "browser-stdout": "1.3.1", 191 | "commander": "2.15.1", 192 | "debug": "3.1.0", 193 | "diff": "3.5.0", 194 | "escape-string-regexp": "1.0.5", 195 | "glob": "7.1.2", 196 | "growl": "1.10.5", 197 | "he": "1.1.1", 198 | "minimatch": "3.0.4", 199 | "mkdirp": "0.5.1", 200 | "supports-color": "5.4.0" 201 | } 202 | }, 203 | "moment": { 204 | "version": "2.22.2", 205 | "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz", 206 | "integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=" 207 | }, 208 | "ms": { 209 | "version": "2.0.0", 210 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 211 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", 212 | "dev": true 213 | }, 214 | "once": { 215 | "version": "1.4.0", 216 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 217 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 218 | "dev": true, 219 | "requires": { 220 | "wrappy": "1" 221 | } 222 | }, 223 | "path-is-absolute": { 224 | "version": "1.0.1", 225 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 226 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 227 | "dev": true 228 | }, 229 | "supports-color": { 230 | "version": "5.4.0", 231 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", 232 | "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", 233 | "dev": true, 234 | "requires": { 235 | "has-flag": "^3.0.0" 236 | } 237 | }, 238 | "type-detect": { 239 | "version": "1.0.0", 240 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-1.0.0.tgz", 241 | "integrity": "sha1-diIXzAbbJY7EiQihKY6LlRIejqI=", 242 | "dev": true 243 | }, 244 | "wrappy": { 245 | "version": "1.0.2", 246 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 247 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 248 | "dev": true 249 | } 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /packages/relieve-failsafe/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "relieve-failsafe", 3 | "version": "2.2.2", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@soyuka/exists-sync": { 8 | "version": "1.0.1", 9 | "resolved": "https://registry.npmjs.org/@soyuka/exists-sync/-/exists-sync-1.0.1.tgz", 10 | "integrity": "sha1-PxCYY8wk6GENF5PZtOliK9zNudo=" 11 | }, 12 | "assertion-error": { 13 | "version": "1.1.0", 14 | "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", 15 | "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", 16 | "dev": true 17 | }, 18 | "balanced-match": { 19 | "version": "1.0.0", 20 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 21 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 22 | "dev": true 23 | }, 24 | "bluebird": { 25 | "version": "3.5.1", 26 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", 27 | "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==", 28 | "dev": true 29 | }, 30 | "brace-expansion": { 31 | "version": "1.1.11", 32 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 33 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 34 | "dev": true, 35 | "requires": { 36 | "balanced-match": "^1.0.0", 37 | "concat-map": "0.0.1" 38 | } 39 | }, 40 | "browser-stdout": { 41 | "version": "1.3.1", 42 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", 43 | "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", 44 | "dev": true 45 | }, 46 | "chai": { 47 | "version": "3.5.0", 48 | "resolved": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz", 49 | "integrity": "sha1-TQJjewZ/6Vi9v906QOxW/vc3Mkc=", 50 | "dev": true, 51 | "requires": { 52 | "assertion-error": "^1.0.1", 53 | "deep-eql": "^0.1.3", 54 | "type-detect": "^1.0.0" 55 | } 56 | }, 57 | "commander": { 58 | "version": "2.15.1", 59 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", 60 | "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", 61 | "dev": true 62 | }, 63 | "concat-map": { 64 | "version": "0.0.1", 65 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 66 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 67 | "dev": true 68 | }, 69 | "debug": { 70 | "version": "2.6.9", 71 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 72 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 73 | "requires": { 74 | "ms": "2.0.0" 75 | } 76 | }, 77 | "deep-eql": { 78 | "version": "0.1.3", 79 | "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", 80 | "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", 81 | "dev": true, 82 | "requires": { 83 | "type-detect": "0.1.1" 84 | }, 85 | "dependencies": { 86 | "type-detect": { 87 | "version": "0.1.1", 88 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", 89 | "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", 90 | "dev": true 91 | } 92 | } 93 | }, 94 | "diff": { 95 | "version": "3.5.0", 96 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", 97 | "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", 98 | "dev": true 99 | }, 100 | "escape-string-regexp": { 101 | "version": "1.0.5", 102 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 103 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 104 | "dev": true 105 | }, 106 | "eventemitter2": { 107 | "version": "2.2.2", 108 | "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-2.2.2.tgz", 109 | "integrity": "sha1-QH6nHCAgzVdTggOrfnpr3Pt2ktU=" 110 | }, 111 | "fs.realpath": { 112 | "version": "1.0.0", 113 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 114 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 115 | "dev": true 116 | }, 117 | "glob": { 118 | "version": "7.1.2", 119 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", 120 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", 121 | "dev": true, 122 | "requires": { 123 | "fs.realpath": "^1.0.0", 124 | "inflight": "^1.0.4", 125 | "inherits": "2", 126 | "minimatch": "^3.0.4", 127 | "once": "^1.3.0", 128 | "path-is-absolute": "^1.0.0" 129 | } 130 | }, 131 | "growl": { 132 | "version": "1.10.5", 133 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", 134 | "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", 135 | "dev": true 136 | }, 137 | "has-flag": { 138 | "version": "3.0.0", 139 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 140 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 141 | "dev": true 142 | }, 143 | "he": { 144 | "version": "1.1.1", 145 | "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", 146 | "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", 147 | "dev": true 148 | }, 149 | "inflight": { 150 | "version": "1.0.6", 151 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 152 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 153 | "dev": true, 154 | "requires": { 155 | "once": "^1.3.0", 156 | "wrappy": "1" 157 | } 158 | }, 159 | "inherits": { 160 | "version": "2.0.3", 161 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 162 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 163 | "dev": true 164 | }, 165 | "ipcee": { 166 | "version": "1.0.6", 167 | "resolved": "https://registry.npmjs.org/ipcee/-/ipcee-1.0.6.tgz", 168 | "integrity": "sha1-PI3I5nh9gdIkyY6POcvvCeBV5tQ=", 169 | "dev": true, 170 | "requires": { 171 | "debug": "^2.2.0", 172 | "eventemitter2": "^2.0.0" 173 | } 174 | }, 175 | "minimatch": { 176 | "version": "3.0.4", 177 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 178 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 179 | "dev": true, 180 | "requires": { 181 | "brace-expansion": "^1.1.7" 182 | } 183 | }, 184 | "minimist": { 185 | "version": "0.0.8", 186 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 187 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", 188 | "dev": true 189 | }, 190 | "mkdirp": { 191 | "version": "0.5.1", 192 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 193 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 194 | "dev": true, 195 | "requires": { 196 | "minimist": "0.0.8" 197 | } 198 | }, 199 | "mocha": { 200 | "version": "5.2.0", 201 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", 202 | "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", 203 | "dev": true, 204 | "requires": { 205 | "browser-stdout": "1.3.1", 206 | "commander": "2.15.1", 207 | "debug": "3.1.0", 208 | "diff": "3.5.0", 209 | "escape-string-regexp": "1.0.5", 210 | "glob": "7.1.2", 211 | "growl": "1.10.5", 212 | "he": "1.1.1", 213 | "minimatch": "3.0.4", 214 | "mkdirp": "0.5.1", 215 | "supports-color": "5.4.0" 216 | }, 217 | "dependencies": { 218 | "debug": { 219 | "version": "3.1.0", 220 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 221 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 222 | "dev": true, 223 | "requires": { 224 | "ms": "2.0.0" 225 | } 226 | } 227 | } 228 | }, 229 | "ms": { 230 | "version": "2.0.0", 231 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 232 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 233 | }, 234 | "once": { 235 | "version": "1.4.0", 236 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 237 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 238 | "dev": true, 239 | "requires": { 240 | "wrappy": "1" 241 | } 242 | }, 243 | "path-is-absolute": { 244 | "version": "1.0.1", 245 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 246 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 247 | "dev": true 248 | }, 249 | "relieve": { 250 | "version": "2.2.2", 251 | "resolved": "https://registry.npmjs.org/relieve/-/relieve-2.2.2.tgz", 252 | "integrity": "sha512-8wOVYlstfu/KkjVSK8kvca90ah6pKHhwy739OOrzc/QFi9nD/nRs6+TwMaTxMxkhqLuLrZOcrIspZNbHzah5Dg==", 253 | "dev": true, 254 | "requires": { 255 | "bluebird": "^3.4.0", 256 | "debug": "^2.2.0", 257 | "eventemitter2": "^2.0.0", 258 | "ipcee": "^1.0.6", 259 | "uuid": "^2.0.2" 260 | } 261 | }, 262 | "supports-color": { 263 | "version": "5.4.0", 264 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", 265 | "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", 266 | "dev": true, 267 | "requires": { 268 | "has-flag": "^3.0.0" 269 | } 270 | }, 271 | "tcpee": { 272 | "version": "1.0.1", 273 | "resolved": "https://registry.npmjs.org/tcpee/-/tcpee-1.0.1.tgz", 274 | "integrity": "sha1-3tLIhguBArLRNkMIgvHEJAzwzkE=", 275 | "requires": { 276 | "eventemitter2": "^2.0.0" 277 | } 278 | }, 279 | "type-detect": { 280 | "version": "1.0.0", 281 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-1.0.0.tgz", 282 | "integrity": "sha1-diIXzAbbJY7EiQihKY6LlRIejqI=", 283 | "dev": true 284 | }, 285 | "uuid": { 286 | "version": "2.0.3", 287 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", 288 | "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=", 289 | "dev": true 290 | }, 291 | "wrappy": { 292 | "version": "1.0.2", 293 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 294 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 295 | "dev": true 296 | } 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /packages/relieve/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "relieve", 3 | "version": "2.2.3", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "ansi-escapes": { 8 | "version": "3.1.0", 9 | "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", 10 | "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", 11 | "dev": true 12 | }, 13 | "ansi-regex": { 14 | "version": "2.1.1", 15 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 16 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", 17 | "dev": true 18 | }, 19 | "ansi-styles": { 20 | "version": "2.2.1", 21 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", 22 | "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", 23 | "dev": true 24 | }, 25 | "array-find-index": { 26 | "version": "1.0.2", 27 | "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", 28 | "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", 29 | "dev": true 30 | }, 31 | "assertion-error": { 32 | "version": "1.1.0", 33 | "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", 34 | "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", 35 | "dev": true 36 | }, 37 | "async": { 38 | "version": "1.5.2", 39 | "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", 40 | "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", 41 | "dev": true 42 | }, 43 | "balanced-match": { 44 | "version": "1.0.0", 45 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 46 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 47 | "dev": true 48 | }, 49 | "bluebird": { 50 | "version": "3.5.1", 51 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", 52 | "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" 53 | }, 54 | "brace-expansion": { 55 | "version": "1.1.11", 56 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 57 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 58 | "dev": true, 59 | "requires": { 60 | "balanced-match": "^1.0.0", 61 | "concat-map": "0.0.1" 62 | } 63 | }, 64 | "browser-stdout": { 65 | "version": "1.3.1", 66 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", 67 | "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", 68 | "dev": true 69 | }, 70 | "builtin-modules": { 71 | "version": "1.1.1", 72 | "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", 73 | "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", 74 | "dev": true 75 | }, 76 | "camelcase": { 77 | "version": "2.1.1", 78 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", 79 | "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", 80 | "dev": true 81 | }, 82 | "camelcase-keys": { 83 | "version": "2.1.0", 84 | "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", 85 | "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", 86 | "dev": true, 87 | "requires": { 88 | "camelcase": "^2.0.0", 89 | "map-obj": "^1.0.0" 90 | } 91 | }, 92 | "chai": { 93 | "version": "3.5.0", 94 | "resolved": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz", 95 | "integrity": "sha1-TQJjewZ/6Vi9v906QOxW/vc3Mkc=", 96 | "dev": true, 97 | "requires": { 98 | "assertion-error": "^1.0.1", 99 | "deep-eql": "^0.1.3", 100 | "type-detect": "^1.0.0" 101 | } 102 | }, 103 | "chalk": { 104 | "version": "1.1.3", 105 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", 106 | "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", 107 | "dev": true, 108 | "requires": { 109 | "ansi-styles": "^2.2.1", 110 | "escape-string-regexp": "^1.0.2", 111 | "has-ansi": "^2.0.0", 112 | "strip-ansi": "^3.0.0", 113 | "supports-color": "^2.0.0" 114 | } 115 | }, 116 | "chardet": { 117 | "version": "0.4.2", 118 | "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", 119 | "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", 120 | "dev": true 121 | }, 122 | "cli-cursor": { 123 | "version": "2.1.0", 124 | "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", 125 | "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", 126 | "dev": true, 127 | "requires": { 128 | "restore-cursor": "^2.0.0" 129 | } 130 | }, 131 | "cli-width": { 132 | "version": "2.2.0", 133 | "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", 134 | "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", 135 | "dev": true 136 | }, 137 | "clone": { 138 | "version": "1.0.4", 139 | "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", 140 | "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", 141 | "dev": true 142 | }, 143 | "cmd-shim": { 144 | "version": "2.0.2", 145 | "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-2.0.2.tgz", 146 | "integrity": "sha1-b8vamUg6j9FdfTChlspp1oii79s=", 147 | "dev": true, 148 | "requires": { 149 | "graceful-fs": "^4.1.2", 150 | "mkdirp": "~0.5.0" 151 | } 152 | }, 153 | "color-convert": { 154 | "version": "1.9.2", 155 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.2.tgz", 156 | "integrity": "sha512-3NUJZdhMhcdPn8vJ9v2UQJoH0qqoGUkYTgFEPZaPjEtwmmKUfNV46zZmgB2M5M4DCEQHMaCfWHCxiBflLm04Tg==", 157 | "dev": true, 158 | "requires": { 159 | "color-name": "1.1.1" 160 | } 161 | }, 162 | "color-name": { 163 | "version": "1.1.1", 164 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.1.tgz", 165 | "integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok=", 166 | "dev": true 167 | }, 168 | "columnify": { 169 | "version": "1.5.4", 170 | "resolved": "https://registry.npmjs.org/columnify/-/columnify-1.5.4.tgz", 171 | "integrity": "sha1-Rzfd8ce2mop8NAVweC6UfuyOeLs=", 172 | "dev": true, 173 | "requires": { 174 | "strip-ansi": "^3.0.0", 175 | "wcwidth": "^1.0.0" 176 | } 177 | }, 178 | "command-join": { 179 | "version": "2.0.0", 180 | "resolved": "https://registry.npmjs.org/command-join/-/command-join-2.0.0.tgz", 181 | "integrity": "sha1-Uui5hPSHLZUv8b3IuYOX0nxxRM8=", 182 | "dev": true 183 | }, 184 | "commander": { 185 | "version": "2.15.1", 186 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", 187 | "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", 188 | "dev": true 189 | }, 190 | "concat-map": { 191 | "version": "0.0.1", 192 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 193 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 194 | "dev": true 195 | }, 196 | "cross-spawn": { 197 | "version": "4.0.2", 198 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", 199 | "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", 200 | "dev": true, 201 | "requires": { 202 | "lru-cache": "^4.0.1", 203 | "which": "^1.2.9" 204 | } 205 | }, 206 | "currently-unhandled": { 207 | "version": "0.4.1", 208 | "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", 209 | "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", 210 | "dev": true, 211 | "requires": { 212 | "array-find-index": "^1.0.1" 213 | } 214 | }, 215 | "debug": { 216 | "version": "2.6.9", 217 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 218 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 219 | "requires": { 220 | "ms": "2.0.0" 221 | } 222 | }, 223 | "decamelize": { 224 | "version": "1.2.0", 225 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", 226 | "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", 227 | "dev": true 228 | }, 229 | "deep-eql": { 230 | "version": "0.1.3", 231 | "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", 232 | "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", 233 | "dev": true, 234 | "requires": { 235 | "type-detect": "0.1.1" 236 | }, 237 | "dependencies": { 238 | "type-detect": { 239 | "version": "0.1.1", 240 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", 241 | "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", 242 | "dev": true 243 | } 244 | } 245 | }, 246 | "defaults": { 247 | "version": "1.0.3", 248 | "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", 249 | "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", 250 | "dev": true, 251 | "requires": { 252 | "clone": "^1.0.2" 253 | } 254 | }, 255 | "diff": { 256 | "version": "3.5.0", 257 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", 258 | "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", 259 | "dev": true 260 | }, 261 | "error-ex": { 262 | "version": "1.3.1", 263 | "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", 264 | "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", 265 | "dev": true, 266 | "requires": { 267 | "is-arrayish": "^0.2.1" 268 | } 269 | }, 270 | "escape-string-regexp": { 271 | "version": "1.0.5", 272 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 273 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 274 | "dev": true 275 | }, 276 | "eventemitter2": { 277 | "version": "2.2.2", 278 | "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-2.2.2.tgz", 279 | "integrity": "sha1-QH6nHCAgzVdTggOrfnpr3Pt2ktU=" 280 | }, 281 | "external-editor": { 282 | "version": "2.2.0", 283 | "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", 284 | "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", 285 | "dev": true, 286 | "requires": { 287 | "chardet": "^0.4.0", 288 | "iconv-lite": "^0.4.17", 289 | "tmp": "^0.0.33" 290 | } 291 | }, 292 | "figures": { 293 | "version": "2.0.0", 294 | "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", 295 | "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", 296 | "dev": true, 297 | "requires": { 298 | "escape-string-regexp": "^1.0.5" 299 | } 300 | }, 301 | "find-up": { 302 | "version": "1.1.2", 303 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", 304 | "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", 305 | "dev": true, 306 | "requires": { 307 | "path-exists": "^2.0.0", 308 | "pinkie-promise": "^2.0.0" 309 | } 310 | }, 311 | "fs.realpath": { 312 | "version": "1.0.0", 313 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 314 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 315 | "dev": true 316 | }, 317 | "get-stdin": { 318 | "version": "4.0.1", 319 | "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", 320 | "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", 321 | "dev": true 322 | }, 323 | "glob": { 324 | "version": "7.1.2", 325 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", 326 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", 327 | "dev": true, 328 | "requires": { 329 | "fs.realpath": "^1.0.0", 330 | "inflight": "^1.0.4", 331 | "inherits": "2", 332 | "minimatch": "^3.0.4", 333 | "once": "^1.3.0", 334 | "path-is-absolute": "^1.0.0" 335 | } 336 | }, 337 | "graceful-fs": { 338 | "version": "4.1.11", 339 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", 340 | "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", 341 | "dev": true 342 | }, 343 | "growl": { 344 | "version": "1.10.5", 345 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", 346 | "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", 347 | "dev": true 348 | }, 349 | "has-ansi": { 350 | "version": "2.0.0", 351 | "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", 352 | "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", 353 | "dev": true, 354 | "requires": { 355 | "ansi-regex": "^2.0.0" 356 | } 357 | }, 358 | "has-flag": { 359 | "version": "3.0.0", 360 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 361 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 362 | "dev": true 363 | }, 364 | "he": { 365 | "version": "1.1.1", 366 | "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", 367 | "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", 368 | "dev": true 369 | }, 370 | "hosted-git-info": { 371 | "version": "2.6.0", 372 | "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.0.tgz", 373 | "integrity": "sha512-lIbgIIQA3lz5XaB6vxakj6sDHADJiZadYEJB+FgA+C4nubM1NwcuvUr9EJPmnH1skZqpqUzWborWo8EIUi0Sdw==", 374 | "dev": true 375 | }, 376 | "iconv-lite": { 377 | "version": "0.4.23", 378 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", 379 | "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", 380 | "dev": true, 381 | "requires": { 382 | "safer-buffer": ">= 2.1.2 < 3" 383 | } 384 | }, 385 | "indent-string": { 386 | "version": "2.1.0", 387 | "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", 388 | "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", 389 | "dev": true, 390 | "requires": { 391 | "repeating": "^2.0.0" 392 | } 393 | }, 394 | "inflight": { 395 | "version": "1.0.6", 396 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 397 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 398 | "dev": true, 399 | "requires": { 400 | "once": "^1.3.0", 401 | "wrappy": "1" 402 | } 403 | }, 404 | "inherits": { 405 | "version": "2.0.3", 406 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 407 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 408 | "dev": true 409 | }, 410 | "inquirer": { 411 | "version": "3.3.0", 412 | "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", 413 | "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", 414 | "dev": true, 415 | "requires": { 416 | "ansi-escapes": "^3.0.0", 417 | "chalk": "^2.0.0", 418 | "cli-cursor": "^2.1.0", 419 | "cli-width": "^2.0.0", 420 | "external-editor": "^2.0.4", 421 | "figures": "^2.0.0", 422 | "lodash": "^4.3.0", 423 | "mute-stream": "0.0.7", 424 | "run-async": "^2.2.0", 425 | "rx-lite": "^4.0.8", 426 | "rx-lite-aggregates": "^4.0.8", 427 | "string-width": "^2.1.0", 428 | "strip-ansi": "^4.0.0", 429 | "through": "^2.3.6" 430 | }, 431 | "dependencies": { 432 | "ansi-regex": { 433 | "version": "3.0.0", 434 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", 435 | "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", 436 | "dev": true 437 | }, 438 | "ansi-styles": { 439 | "version": "3.2.1", 440 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 441 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 442 | "dev": true, 443 | "requires": { 444 | "color-convert": "^1.9.0" 445 | } 446 | }, 447 | "chalk": { 448 | "version": "2.4.1", 449 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", 450 | "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", 451 | "dev": true, 452 | "requires": { 453 | "ansi-styles": "^3.2.1", 454 | "escape-string-regexp": "^1.0.5", 455 | "supports-color": "^5.3.0" 456 | } 457 | }, 458 | "strip-ansi": { 459 | "version": "4.0.0", 460 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", 461 | "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", 462 | "dev": true, 463 | "requires": { 464 | "ansi-regex": "^3.0.0" 465 | } 466 | }, 467 | "supports-color": { 468 | "version": "5.4.0", 469 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", 470 | "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", 471 | "dev": true, 472 | "requires": { 473 | "has-flag": "^3.0.0" 474 | } 475 | } 476 | } 477 | }, 478 | "ipcee": { 479 | "version": "1.0.6", 480 | "resolved": "https://registry.npmjs.org/ipcee/-/ipcee-1.0.6.tgz", 481 | "integrity": "sha1-PI3I5nh9gdIkyY6POcvvCeBV5tQ=", 482 | "requires": { 483 | "debug": "^2.2.0", 484 | "eventemitter2": "^2.0.0" 485 | } 486 | }, 487 | "is-arrayish": { 488 | "version": "0.2.1", 489 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", 490 | "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", 491 | "dev": true 492 | }, 493 | "is-builtin-module": { 494 | "version": "1.0.0", 495 | "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", 496 | "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", 497 | "dev": true, 498 | "requires": { 499 | "builtin-modules": "^1.0.0" 500 | } 501 | }, 502 | "is-finite": { 503 | "version": "1.0.2", 504 | "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", 505 | "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", 506 | "dev": true, 507 | "requires": { 508 | "number-is-nan": "^1.0.0" 509 | } 510 | }, 511 | "is-fullwidth-code-point": { 512 | "version": "2.0.0", 513 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", 514 | "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", 515 | "dev": true 516 | }, 517 | "is-promise": { 518 | "version": "2.1.0", 519 | "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", 520 | "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", 521 | "dev": true 522 | }, 523 | "is-utf8": { 524 | "version": "0.2.1", 525 | "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", 526 | "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", 527 | "dev": true 528 | }, 529 | "isexe": { 530 | "version": "2.0.0", 531 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 532 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", 533 | "dev": true 534 | }, 535 | "lerna": { 536 | "version": "2.0.0-beta.38", 537 | "resolved": "https://registry.npmjs.org/lerna/-/lerna-2.0.0-beta.38.tgz", 538 | "integrity": "sha1-mSNkFqZplwczbcve74PDFdH3GDM=", 539 | "dev": true, 540 | "requires": { 541 | "async": "^1.5.0", 542 | "chalk": "^1.1.1", 543 | "cmd-shim": "^2.0.2", 544 | "columnify": "^1.5.4", 545 | "command-join": "^2.0.0", 546 | "cross-spawn": "^4.0.0", 547 | "glob": "^7.0.6", 548 | "inquirer": "^3.0.1", 549 | "lodash": "^4.17.4", 550 | "meow": "^3.7.0", 551 | "minimatch": "^3.0.0", 552 | "mkdirp": "^0.5.1", 553 | "normalize-path": "^2.0.1", 554 | "object-assign-sorted": "^2.0.1", 555 | "path-exists": "^2.1.0", 556 | "progress": "^1.1.8", 557 | "read-cmd-shim": "^1.0.1", 558 | "rimraf": "^2.4.4", 559 | "semver": "^5.1.0", 560 | "signal-exit": "^3.0.2" 561 | } 562 | }, 563 | "load-json-file": { 564 | "version": "1.1.0", 565 | "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", 566 | "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", 567 | "dev": true, 568 | "requires": { 569 | "graceful-fs": "^4.1.2", 570 | "parse-json": "^2.2.0", 571 | "pify": "^2.0.0", 572 | "pinkie-promise": "^2.0.0", 573 | "strip-bom": "^2.0.0" 574 | } 575 | }, 576 | "lodash": { 577 | "version": "4.17.19", 578 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", 579 | "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", 580 | "dev": true 581 | }, 582 | "loud-rejection": { 583 | "version": "1.6.0", 584 | "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", 585 | "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", 586 | "dev": true, 587 | "requires": { 588 | "currently-unhandled": "^0.4.1", 589 | "signal-exit": "^3.0.0" 590 | } 591 | }, 592 | "lru-cache": { 593 | "version": "4.1.3", 594 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", 595 | "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", 596 | "dev": true, 597 | "requires": { 598 | "pseudomap": "^1.0.2", 599 | "yallist": "^2.1.2" 600 | } 601 | }, 602 | "map-obj": { 603 | "version": "1.0.1", 604 | "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", 605 | "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", 606 | "dev": true 607 | }, 608 | "meow": { 609 | "version": "3.7.0", 610 | "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", 611 | "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", 612 | "dev": true, 613 | "requires": { 614 | "camelcase-keys": "^2.0.0", 615 | "decamelize": "^1.1.2", 616 | "loud-rejection": "^1.0.0", 617 | "map-obj": "^1.0.1", 618 | "minimist": "^1.1.3", 619 | "normalize-package-data": "^2.3.4", 620 | "object-assign": "^4.0.1", 621 | "read-pkg-up": "^1.0.1", 622 | "redent": "^1.0.0", 623 | "trim-newlines": "^1.0.0" 624 | }, 625 | "dependencies": { 626 | "minimist": { 627 | "version": "1.2.0", 628 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", 629 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", 630 | "dev": true 631 | } 632 | } 633 | }, 634 | "mimic-fn": { 635 | "version": "1.2.0", 636 | "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", 637 | "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", 638 | "dev": true 639 | }, 640 | "minimatch": { 641 | "version": "3.0.4", 642 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 643 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 644 | "dev": true, 645 | "requires": { 646 | "brace-expansion": "^1.1.7" 647 | } 648 | }, 649 | "minimist": { 650 | "version": "0.0.8", 651 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 652 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", 653 | "dev": true 654 | }, 655 | "mkdirp": { 656 | "version": "0.5.1", 657 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 658 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 659 | "dev": true, 660 | "requires": { 661 | "minimist": "0.0.8" 662 | } 663 | }, 664 | "mocha": { 665 | "version": "5.2.0", 666 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", 667 | "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", 668 | "dev": true, 669 | "requires": { 670 | "browser-stdout": "1.3.1", 671 | "commander": "2.15.1", 672 | "debug": "3.1.0", 673 | "diff": "3.5.0", 674 | "escape-string-regexp": "1.0.5", 675 | "glob": "7.1.2", 676 | "growl": "1.10.5", 677 | "he": "1.1.1", 678 | "minimatch": "3.0.4", 679 | "mkdirp": "0.5.1", 680 | "supports-color": "5.4.0" 681 | }, 682 | "dependencies": { 683 | "debug": { 684 | "version": "3.1.0", 685 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 686 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 687 | "dev": true, 688 | "requires": { 689 | "ms": "2.0.0" 690 | } 691 | }, 692 | "supports-color": { 693 | "version": "5.4.0", 694 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", 695 | "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", 696 | "dev": true, 697 | "requires": { 698 | "has-flag": "^3.0.0" 699 | } 700 | } 701 | } 702 | }, 703 | "ms": { 704 | "version": "2.0.0", 705 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 706 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 707 | }, 708 | "mute-stream": { 709 | "version": "0.0.7", 710 | "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", 711 | "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", 712 | "dev": true 713 | }, 714 | "normalize-package-data": { 715 | "version": "2.4.0", 716 | "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", 717 | "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", 718 | "dev": true, 719 | "requires": { 720 | "hosted-git-info": "^2.1.4", 721 | "is-builtin-module": "^1.0.0", 722 | "semver": "2 || 3 || 4 || 5", 723 | "validate-npm-package-license": "^3.0.1" 724 | } 725 | }, 726 | "normalize-path": { 727 | "version": "2.1.1", 728 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", 729 | "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", 730 | "dev": true, 731 | "requires": { 732 | "remove-trailing-separator": "^1.0.1" 733 | } 734 | }, 735 | "number-is-nan": { 736 | "version": "1.0.1", 737 | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", 738 | "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", 739 | "dev": true 740 | }, 741 | "object-assign": { 742 | "version": "4.1.1", 743 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 744 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", 745 | "dev": true 746 | }, 747 | "object-assign-sorted": { 748 | "version": "2.0.1", 749 | "resolved": "https://registry.npmjs.org/object-assign-sorted/-/object-assign-sorted-2.0.1.tgz", 750 | "integrity": "sha1-yZg/qePtWAe0nPGplDN48kXZOVs=", 751 | "dev": true, 752 | "requires": { 753 | "sorted-object": "^2.0.0" 754 | } 755 | }, 756 | "once": { 757 | "version": "1.4.0", 758 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 759 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 760 | "dev": true, 761 | "requires": { 762 | "wrappy": "1" 763 | } 764 | }, 765 | "onetime": { 766 | "version": "2.0.1", 767 | "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", 768 | "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", 769 | "dev": true, 770 | "requires": { 771 | "mimic-fn": "^1.0.0" 772 | } 773 | }, 774 | "os-tmpdir": { 775 | "version": "1.0.2", 776 | "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", 777 | "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", 778 | "dev": true 779 | }, 780 | "parse-json": { 781 | "version": "2.2.0", 782 | "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", 783 | "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", 784 | "dev": true, 785 | "requires": { 786 | "error-ex": "^1.2.0" 787 | } 788 | }, 789 | "path-exists": { 790 | "version": "2.1.0", 791 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", 792 | "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", 793 | "dev": true, 794 | "requires": { 795 | "pinkie-promise": "^2.0.0" 796 | } 797 | }, 798 | "path-is-absolute": { 799 | "version": "1.0.1", 800 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 801 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 802 | "dev": true 803 | }, 804 | "path-type": { 805 | "version": "1.1.0", 806 | "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", 807 | "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", 808 | "dev": true, 809 | "requires": { 810 | "graceful-fs": "^4.1.2", 811 | "pify": "^2.0.0", 812 | "pinkie-promise": "^2.0.0" 813 | } 814 | }, 815 | "pify": { 816 | "version": "2.3.0", 817 | "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", 818 | "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", 819 | "dev": true 820 | }, 821 | "pinkie": { 822 | "version": "2.0.4", 823 | "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", 824 | "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", 825 | "dev": true 826 | }, 827 | "pinkie-promise": { 828 | "version": "2.0.1", 829 | "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", 830 | "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", 831 | "dev": true, 832 | "requires": { 833 | "pinkie": "^2.0.0" 834 | } 835 | }, 836 | "progress": { 837 | "version": "1.1.8", 838 | "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", 839 | "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", 840 | "dev": true 841 | }, 842 | "pseudomap": { 843 | "version": "1.0.2", 844 | "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", 845 | "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", 846 | "dev": true 847 | }, 848 | "read-cmd-shim": { 849 | "version": "1.0.1", 850 | "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-1.0.1.tgz", 851 | "integrity": "sha1-LV0Vd4ajfAVdIgd8MsU/gynpHHs=", 852 | "dev": true, 853 | "requires": { 854 | "graceful-fs": "^4.1.2" 855 | } 856 | }, 857 | "read-pkg": { 858 | "version": "1.1.0", 859 | "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", 860 | "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", 861 | "dev": true, 862 | "requires": { 863 | "load-json-file": "^1.0.0", 864 | "normalize-package-data": "^2.3.2", 865 | "path-type": "^1.0.0" 866 | } 867 | }, 868 | "read-pkg-up": { 869 | "version": "1.0.1", 870 | "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", 871 | "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", 872 | "dev": true, 873 | "requires": { 874 | "find-up": "^1.0.0", 875 | "read-pkg": "^1.0.0" 876 | } 877 | }, 878 | "redent": { 879 | "version": "1.0.0", 880 | "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", 881 | "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", 882 | "dev": true, 883 | "requires": { 884 | "indent-string": "^2.1.0", 885 | "strip-indent": "^1.0.1" 886 | } 887 | }, 888 | "remove-trailing-separator": { 889 | "version": "1.1.0", 890 | "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", 891 | "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", 892 | "dev": true 893 | }, 894 | "repeating": { 895 | "version": "2.0.1", 896 | "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", 897 | "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", 898 | "dev": true, 899 | "requires": { 900 | "is-finite": "^1.0.0" 901 | } 902 | }, 903 | "restore-cursor": { 904 | "version": "2.0.0", 905 | "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", 906 | "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", 907 | "dev": true, 908 | "requires": { 909 | "onetime": "^2.0.0", 910 | "signal-exit": "^3.0.2" 911 | } 912 | }, 913 | "rimraf": { 914 | "version": "2.6.2", 915 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", 916 | "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", 917 | "dev": true, 918 | "requires": { 919 | "glob": "^7.0.5" 920 | } 921 | }, 922 | "run-async": { 923 | "version": "2.3.0", 924 | "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", 925 | "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", 926 | "dev": true, 927 | "requires": { 928 | "is-promise": "^2.1.0" 929 | } 930 | }, 931 | "rx-lite": { 932 | "version": "4.0.8", 933 | "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", 934 | "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", 935 | "dev": true 936 | }, 937 | "rx-lite-aggregates": { 938 | "version": "4.0.8", 939 | "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", 940 | "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", 941 | "dev": true, 942 | "requires": { 943 | "rx-lite": "*" 944 | } 945 | }, 946 | "safer-buffer": { 947 | "version": "2.1.2", 948 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 949 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 950 | "dev": true 951 | }, 952 | "semver": { 953 | "version": "5.5.0", 954 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", 955 | "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", 956 | "dev": true 957 | }, 958 | "signal-exit": { 959 | "version": "3.0.2", 960 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", 961 | "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", 962 | "dev": true 963 | }, 964 | "sorted-object": { 965 | "version": "2.0.1", 966 | "resolved": "https://registry.npmjs.org/sorted-object/-/sorted-object-2.0.1.tgz", 967 | "integrity": "sha1-fWMfS9OnmKJK8d/8+/6DM3pd9fw=", 968 | "dev": true 969 | }, 970 | "spdx-correct": { 971 | "version": "3.0.0", 972 | "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", 973 | "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", 974 | "dev": true, 975 | "requires": { 976 | "spdx-expression-parse": "^3.0.0", 977 | "spdx-license-ids": "^3.0.0" 978 | } 979 | }, 980 | "spdx-exceptions": { 981 | "version": "2.1.0", 982 | "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", 983 | "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==", 984 | "dev": true 985 | }, 986 | "spdx-expression-parse": { 987 | "version": "3.0.0", 988 | "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", 989 | "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", 990 | "dev": true, 991 | "requires": { 992 | "spdx-exceptions": "^2.1.0", 993 | "spdx-license-ids": "^3.0.0" 994 | } 995 | }, 996 | "spdx-license-ids": { 997 | "version": "3.0.0", 998 | "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", 999 | "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==", 1000 | "dev": true 1001 | }, 1002 | "string-width": { 1003 | "version": "2.1.1", 1004 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", 1005 | "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", 1006 | "dev": true, 1007 | "requires": { 1008 | "is-fullwidth-code-point": "^2.0.0", 1009 | "strip-ansi": "^4.0.0" 1010 | }, 1011 | "dependencies": { 1012 | "ansi-regex": { 1013 | "version": "3.0.0", 1014 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", 1015 | "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", 1016 | "dev": true 1017 | }, 1018 | "strip-ansi": { 1019 | "version": "4.0.0", 1020 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", 1021 | "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", 1022 | "dev": true, 1023 | "requires": { 1024 | "ansi-regex": "^3.0.0" 1025 | } 1026 | } 1027 | } 1028 | }, 1029 | "strip-ansi": { 1030 | "version": "3.0.1", 1031 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 1032 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 1033 | "dev": true, 1034 | "requires": { 1035 | "ansi-regex": "^2.0.0" 1036 | } 1037 | }, 1038 | "strip-bom": { 1039 | "version": "2.0.0", 1040 | "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", 1041 | "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", 1042 | "dev": true, 1043 | "requires": { 1044 | "is-utf8": "^0.2.0" 1045 | } 1046 | }, 1047 | "strip-indent": { 1048 | "version": "1.0.1", 1049 | "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", 1050 | "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", 1051 | "dev": true, 1052 | "requires": { 1053 | "get-stdin": "^4.0.1" 1054 | } 1055 | }, 1056 | "supports-color": { 1057 | "version": "2.0.0", 1058 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", 1059 | "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", 1060 | "dev": true 1061 | }, 1062 | "through": { 1063 | "version": "2.3.8", 1064 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 1065 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", 1066 | "dev": true 1067 | }, 1068 | "tmp": { 1069 | "version": "0.0.33", 1070 | "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", 1071 | "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", 1072 | "dev": true, 1073 | "requires": { 1074 | "os-tmpdir": "~1.0.2" 1075 | } 1076 | }, 1077 | "trim-newlines": { 1078 | "version": "1.0.0", 1079 | "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", 1080 | "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", 1081 | "dev": true 1082 | }, 1083 | "type-detect": { 1084 | "version": "1.0.0", 1085 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-1.0.0.tgz", 1086 | "integrity": "sha1-diIXzAbbJY7EiQihKY6LlRIejqI=", 1087 | "dev": true 1088 | }, 1089 | "uuid": { 1090 | "version": "2.0.3", 1091 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", 1092 | "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=" 1093 | }, 1094 | "validate-npm-package-license": { 1095 | "version": "3.0.3", 1096 | "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz", 1097 | "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==", 1098 | "dev": true, 1099 | "requires": { 1100 | "spdx-correct": "^3.0.0", 1101 | "spdx-expression-parse": "^3.0.0" 1102 | } 1103 | }, 1104 | "wcwidth": { 1105 | "version": "1.0.1", 1106 | "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", 1107 | "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", 1108 | "dev": true, 1109 | "requires": { 1110 | "defaults": "^1.0.3" 1111 | } 1112 | }, 1113 | "which": { 1114 | "version": "1.3.1", 1115 | "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", 1116 | "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", 1117 | "dev": true, 1118 | "requires": { 1119 | "isexe": "^2.0.0" 1120 | } 1121 | }, 1122 | "wrappy": { 1123 | "version": "1.0.2", 1124 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1125 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 1126 | "dev": true 1127 | }, 1128 | "yallist": { 1129 | "version": "2.1.2", 1130 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", 1131 | "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", 1132 | "dev": true 1133 | } 1134 | } 1135 | } 1136 | --------------------------------------------------------------------------------