diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..f99eec3 --- /dev/null +++ b/.babelrc @@ -0,0 +1,8 @@ +{ + "presets": ["es2015", "react", "stage-0"], + + "plugins": [ + "add-module-exports", + "transform-decorators-legacy" + ] +} \ No newline at end of file diff --git a/.cache/.cache_15qy1o4 b/.cache/.cache_15qy1o4 new file mode 100644 index 0000000..f4a7c41 --- /dev/null +++ b/.cache/.cache_15qy1o4 @@ -0,0 +1 @@ +{"/Users/roy/royws/shell-executor/source/src/process.js":{"size":26,"mtime":1483338490000,"hashOfConfig":"suduii","results":{"filePath":"/Users/roy/royws/shell-executor/source/src/process.js","messages":[],"errorCount":0,"warningCount":0}},"/Users/roy/royws/shell-executor/source/bin/cli.js":{"size":182,"mtime":1484751608000,"hashOfConfig":"suduii","results":{"filePath":"/Users/roy/royws/shell-executor/source/bin/cli.js","messages":[],"errorCount":0,"warningCount":0}},"/Users/roy/royws/shell-executor/source/src/options.js":{"size":676,"mtime":1484751608000,"hashOfConfig":"suduii","results":{"filePath":"/Users/roy/royws/shell-executor/source/src/options.js","messages":[],"errorCount":0,"warningCount":0}},"/Users/roy/royws/shell-executor/source/time-manager.js":{"size":390,"mtime":1484751608000,"hashOfConfig":"suduii","results":{"filePath":"/Users/roy/royws/shell-executor/source/time-manager.js","messages":[],"errorCount":0,"warningCount":0}},"/Users/roy/royws/shell-executor/source/index.js":{"size":2879,"mtime":1484751986000,"hashOfConfig":"suduii","results":{"filePath":"/Users/roy/royws/shell-executor/source/index.js","messages":[],"errorCount":0,"warningCount":0}},"/Users/roy/royws/shell-executor/source/src/main.js":{"size":3272,"mtime":1484752318000,"hashOfConfig":"suduii","results":{"filePath":"/Users/roy/royws/shell-executor/source/src/main.js","messages":[],"errorCount":0,"warningCount":0}},"/Users/roy/royws/shell-executor/specs/index.spec.js":{"size":1072,"mtime":1484752145000,"hashOfConfig":"suduii","results":{"filePath":"/Users/roy/royws/shell-executor/specs/index.spec.js","messages":[],"errorCount":0,"warningCount":0}}} \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..0a95edb --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,177 @@ +module.exports = { + "parser": "babel-eslint", + "plugins": [ + "import", + "mocha", + "jsx-a11y", + "react", + ], + "extends": ["eslint:recommended", "airbnb"], + "rules": { + "mocha/no-exclusive-tests": 2, + "eqeqeq": [2, "smart"], + "curly": 2, + "quotes": [2, "single", "avoid-escape"], + "strict": 0, + "no-unused-expressions": 0, + "no-underscore-dangle": 0, + "no-unused-vars": [2, { "vars": "all", "args": "after-used" }], + "no-spaced-func" : 0, + "jsx-a11y/anchor-has-content": 0, + "no-plusplus": ["error", { "allowForLoopAfterthoughts": true }], + "func-names": 2, + "no-shadow": 2, + "camelcase": 2, + "new-cap": [2, {"capIsNewExceptions": ["Then", "When", "Given", "AfterFeatures", "After", "BeforeFeatures", "Before", "BeforeFeature"]}], + "dot-notation": 2, + "no-native-reassign": 1, + "no-new": 1, + "no-confusing-arrow": [2, {"allowParens": true}], + "no-console": 0, + "no-constant-condition": 1, + "object-curly-spacing": 2, + "consistent-return": 2, + "jsx-quotes": 1, + "newline-per-chained-call": 0, + "no-unneeded-ternary": [2, {"defaultAssignment": true }], + "no-extra-strict": 0, + "no-alert": 2, + "no-array-constructor": 2, + "no-caller": 2, + "no-catch-shadow": 2, + "no-eval": 2, + "no-extend-native": 2, + "no-extra-bind": 2, + "no-implied-eval": 2, + "no-iterator": 2, + "no-label-var": 2, + "no-labels": 2, + "no-lone-blocks": 2, + "no-loop-func": 2, + "no-multi-spaces": 0, + "no-multi-str": 2, + "no-new-func": 2, + "no-new-object": 2, + "no-new-wrappers": 2, + "no-octal-escape": 2, + "no-process-exit": 2, + "no-proto": 2, + "no-return-assign": 2, + "no-script-url": 2, + "no-sequences": 2, + "no-undef": 2, + "no-shadow-restricted-names": 2, + "no-trailing-spaces": 2, + "quote-props": 2, + "object-shorthand": 2, + "prefer-arrow-callback": 2, + "template-curly-spacing": 0, + "no-undef-init": 2, + "id-length": 0, + "no-use-before-define": 2, + "no-with": 2, + "comma-spacing": 2, + "eol-last": 2, + "padded-blocks": 0, + "no-extra-parens": [2, "functions"], + "key-spacing": [2, { "beforeColon": false, "afterColon": true }], + "new-parens": 2, + "semi": 2, + "semi-spacing": [2, {"before": false, "after": true}], + "space-infix-ops": 2, + "keyword-spacing": 2, + "space-unary-ops": [2, { "words": true, "nonwords": false }], + "yoda": [2, "never"], + "indent": 0, + "vars-on-top": 0, + "max-len": 0, + "no-param-reassign": 0, + "arrow-body-style": 2, + "brace-style": 2, + "prefer-template": 2, + "computed-property-spacing": 1, + "space-in-parens": 1, + "no-useless-constructor": 2, + "prefer-rest-params": 2, + "array-bracket-spacing": 1, + "no-case-declarations": 2, + "array-callback-return": 2, + "prefer-const": 2, + "global-require": 2, + "no-useless-escape": 2, + "no-restricted-syntax": 2, + "no-duplicate-imports": [2, { "includeExports": true }], + "import/no-duplicates": [0, { "commonjs": true }], + "import/no-unresolved": [0, { "commonjs": true }], + "import/export": 1, + "jsx-a11y/img-has-alt": 0, + "react/jsx-equals-spacing": [1, "never"], + "react/display-name": 0, + "react/jsx-no-undef": 1, + "react/jsx-no-bind": 2, + "react/jsx-curly-spacing": [2, "always"], + "react/jsx-first-prop-new-line": [2, "never"], + "react/jsx-indent": [0, 2], + "react/jsx-boolean-value": 0, + "react/jsx-sort-prop-types": 0, + "react/jsx-sort-props": 0, + "react/jsx-uses-react": 1, + "react/jsx-uses-vars": 1, + "react/no-did-mount-set-state": 1, + "react/no-did-update-set-state": 1, + "react/jsx-closing-bracket-location": 0, + "react/jsx-space-before-closing": 2, + "react/no-multi-comp": 1, + "react/no-unknown-property": 1, + "react/prop-types": 0, + "react/react-in-jsx-scope": 1, + "react/self-closing-comp": 2, + "react/sort-comp": 0, + "react/wrap-multilines": 0, + "react/jsx-indent-props": 0, + "react/prefer-stateless-function": 1, + "generator-star-spacing": 0, + "import/no-extraneous-dependencies": 0, + "linebreak-style": 2, + "import/imports-first": 0, + "react/no-string-refs": 0, + "react/jsx-filename-extension": 0, + "react/jsx-wrap-multilines": 0, + "no-mixed-operators": 0, + "import/prefer-default-export": 0, + "import/newline-after-import": 0, + "require-yield": 1, + "no-extra-boolean-cast": 2, + "no-continue": 2, + "object-property-newline": 1, + "no-prototype-builtins": 2, + "operator-assignment": 2, + "jsx-a11y/label-has-for": 2, + "react/no-find-dom-node": 2, + "no-lonely-if": 2, + "dot-location": 2, + "import/no-named-as-default": 1, + "prefer-spread": 1 + }, + "env": { + "browser": true, + "node": true, + "mocha": true, + "es6": true + }, + "ecmaFeatures": { + "jsx" : true, + "modules": true + }, + "globals": { + "__MOBX_DEVTOOLS__": true, + "__DEVELOPMENT__": true, + "__CLIENT__": true, + "__SERVER__": true, + "__DISABLE_SSR__": true, + "__DEVTOOLS__": true, + "socket": true, + "jest": true, + "expect": true, + } +} diff --git a/.gitignore b/.gitignore index 748855d..5595f49 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,4 @@ build/Release # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git node_modules .eslintrc +dist/ diff --git a/bin/cli.js b/bin/cli.js deleted file mode 100755 index f87e3b0..0000000 --- a/bin/cli.js +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env node - -var main = require( '../src/main' ); - -var cliLauncher = require( 'clix' ); -cliLauncher.launch( require( '../src/options' ), function ( program ) { - main.run( program ); -} ); diff --git a/index.js b/index.js index 91b615b..f843a90 100644 --- a/index.js +++ b/index.js @@ -1,115 +1 @@ -var spawnly = require( 'spawnly' ); -var extend = require( 'extend' ); -var dispatchy = require( 'dispatchy' ); -var Promise = require( 'es6-promise' ).Promise; - -var timeManager = require( './time-manager' ); - -function streamToString( stream ) { - var chunks = [ ]; - return new Promise( function ( resolve, reject ) { - if ( !stream.readable ) { - return resolve( '' ); - } - stream.on( 'data', function ( chunk ) { - chunks.push( chunk.toString() ); - } ); - stream.on( 'end', function () { - resolve( chunks.join( '' ) ); - } ); - stream.on( 'error', function ( err ) { - reject( err ); - } ); - } ); -} - -module.exports = { - create: function () { - var commands = [ ]; - - return extend( dispatchy.create(), { - runCmds: function ( cmds, options ) { - var me = this; - cmds = cmds || [ ]; - - var promises = cmds.map( function ( cmd ) { - return me.run( cmd, options ); - } ); - - return Promise.all( promises ); - }, - run: function ( cmd, options ) { - var me = this; - - var opts = extend( { stdio: 'inherit' }, options ); - - var timer = timeManager.start(); - - var cp = spawnly( cmd, opts ); - - me.fire( 'command:start', { cp: cp, cmd: cmd } ); - - cp.cmd = cmd; - commands.push( cp ); - - return new Promise( function ( resolve, reject ) { - cp.on( 'exit', function ( exitCode ) { - var res = timer.stop(); - - var stdoutPromise = Promise.resolve( '' ); - var stderrPromise = Promise.resolve( '' ); - - if ( cp.stdout ) { - stdoutPromise = streamToString( cp.stdout ); - } - - if ( cp.stderr ) { - stderrPromise = streamToString( cp.stderr ); - } - - Promise.all( [ stdoutPromise, stderrPromise ] ).then( function ( results ) { - var args = { - cp: cp, - stdout: results[ 0 ], - stderr: results[ 1 ], - cmd: cmd, - exitCode: exitCode, - duration: res.diff, - durationFormatted: res.diffFormatted - }; - me.fire( 'command:exit', args ); - resolve( args ); - } ); - - } ); - - cp.on( 'error', function ( err ) { - err = err || { }; - var res = timer.stop(); - err.duration = res.diff; - err.durationFormatted = res.diffFormatted; - me.fire( 'command:error', err ); - reject( err ); - } ); - } ); - }, - getKillCommand: function () { - return 'kill -9 ' + commands.map( function ( cmd ) { - return cmd.pid; - } ).join( ' ' ); - }, - stopAll: function () { - var me = this; - commands.forEach( function ( cp ) { - if ( !cp.exitCode ) { - - cp.removeAllListeners( 'exit' ); - cp.removeAllListeners( 'error' ); - me.fire( 'command:killed', cp ); - cp.kill( 'SIGINT' ); - } - } ); - } - } ); - } -}; +module.exports = require('./dist/index.js'); diff --git a/package.json b/package.json index 12b99f4..2507716 100644 --- a/package.json +++ b/package.json @@ -4,26 +4,26 @@ "description": "A small nodejs module to execute shell commands in parallel", "main": "index.js", "scripts": { - "beautify": "esbeautifier './index.js' './src/**/*.js' './specs/**/*.js' './bin/**/*.js'", - "beautify-check": "esbeautifier -k './index.js' './src/**/*.js' './specs/**/*.js' './bin/**/*.js'", - "eslint": "eslinter './index.js' './src/**/*.js' './specs/**/*.js' './bin/**/*.js'", - "lint": "npm run beautify && npm run eslint", - "test": "mocha-runner './specs/**/*.js'", - "cover": "istanbul cover -x 'specs/**/*.js' mocha-runner './specs/**/*.js' html text-summary", - "watch": "watch-spawn -i -p './specs/**/*.js' npm run cover", - "check": "npm run beautify-check && npm run eslint", - "verify": "npm run check --silent && npm test --silent", + "autofix": "npm run eslint -- --fix", + "_lint": "eslint --cache --cache-location='.cache/' -f 'node_modules/eslint-friendly-formatter' ", + "eslint": "npm run _lint -- 'source/**/*.js' 'specs/**/*.spec.js' ", + "test": "npm run verify && babel-node node_modules/.bin/mocha-runner 'specs/**/*.spec.js'", + "cover": "istanbul cover -x 'specs/**/*.spec.js' babel-node node_modules/.bin/mocha-runner 'specs/**/*.spec.js' html text-summary", + "watch": "npm run cover && watch-spawn -p 'specs/**/*.spec.js' npm run cover", + "verify": "npm run eslint", "changelog": "changelogx -f markdown -o ./changelog.md", - "do-changelog": "npm run changelog && git add ./changelog.md && git commit -m 'DOC: Generate changelog'", + "do-changelog": "npm run changelog && git add ./changelog.md && git commit -m 'DOC: Generate changelog' --no-verify", "install-hooks": "prepush install && changelogx install-hook && precommit install", - "pre-v": "npm run verify", + "pre-v": "npm run test", "post-v": "npm run do-changelog && git push --no-verify && git push --tags --no-verify", "bump-major": "npm run pre-v && npm version major -m 'BLD: Release v%s' && npm run post-v", "bump-minor": "npm run pre-v && npm version minor -m 'BLD: Release v%s' && npm run post-v", - "bump-patch": "npm run pre-v && npm version patch -m 'BLD: Release v%s' && npm run post-v" + "bump-patch": "npm run pre-v && npm version patch -m 'BLD: Release v%s' && npm run post-v", + "prepublish": "npm run build", + "build": "babel source/ -d dist/" }, "bin": { - "shell-exec": "./bin/cli.js" + "shell-exec": "./dist/bin/cli.js" }, "repository": { "type": "git", @@ -45,9 +45,22 @@ "onDirtyState": "stash" }, "devDependencies": { + "babel-cli": "6.18.0", + "babel-eslint": "7.1.1", + "babel-plugin-add-module-exports": "0.2.1", + "babel-plugin-transform-decorators-legacy": "1.3.4", + "babel-polyfill": "6.16.0", + "babel-preset-es2015": "6.18.0", + "babel-preset-react": "6.16.0", + "babel-preset-stage-0": "6.16.0", "changelogx": "2.0.1", - "esbeautifier": "^4.0.1", - "eslinter": "^2.1.0", + "eslint": "3.13.1", + "eslint-config-airbnb": "14.0.0", + "eslint-friendly-formatter": "2.0.7", + "eslint-plugin-import": "2.2.0", + "eslint-plugin-jsx-a11y": "3.0.2", + "eslint-plugin-mocha": "4.8.0", + "eslint-plugin-react": "6.9.0", "istanbul": "^0.3.17", "mocha-runner": "^1.0.8", "precommit": "1.2.2", @@ -56,6 +69,8 @@ "watch-spawn": "2.0.0" }, "dependencies": { + "blessed": "0.1.81", + "blessed-contrib": "4.7.5", "clix": "2.2.1", "dispatchy": "1.0.3", "es6-promise": "4.0.5", diff --git a/source/bin/cli.js b/source/bin/cli.js new file mode 100755 index 0000000..d88895e --- /dev/null +++ b/source/bin/cli.js @@ -0,0 +1,6 @@ +#!/usr/bin/env node +require('babel-polyfill'); +const main = require('../src/main'); + +const cliLauncher = require('clix'); +cliLauncher.launch(require('../src/options'), program => main.run(program)); diff --git a/source/index.js b/source/index.js new file mode 100644 index 0000000..6908f39 --- /dev/null +++ b/source/index.js @@ -0,0 +1,116 @@ +const spawnly = require('spawnly'); +const extend = require('extend'); +const dispatchy = require('dispatchy'); +const Promise = require('es6-promise').Promise; + +const timeManager = require('./time-manager'); + +function streamToString(stream) { + const chunks = []; + return new Promise((resolve, reject) => { + if (!stream.readable) { + resolve(''); + return; + } + stream.on('data', (chunk) => { + chunks.push(chunk.toString()); + }); + stream.on('end', () => { + resolve(chunks.join('')); + }); + stream.on('error', (err) => { + reject(err); + }); + }); +} + +module.exports = { + create() { + const commands = []; + + return extend(dispatchy.create(), { + runCmds(cmds, options) { + const me = this; + cmds = cmds || []; + + const promises = cmds.map(cmd => me.run(cmd, options)); + + return Promise.all(promises); + }, + run(cmd, options) { + const me = this; + + const opts = extend({ stdio: 'inherit' }, options); + + const timer = timeManager.start(); + + const cp = spawnly(cmd, opts); + + me.fire('command:start', { cp, cmd }); + + cp.cmd = cmd; + commands.push(cp); + + const commandPromise = new Promise((resolve, reject) => { + cp.on('exit', (exitCode) => { + const res = timer.stop(); + + let stdoutPromise = Promise.resolve(''); + let stderrPromise = Promise.resolve(''); + + if (cp.stdout) { + stdoutPromise = streamToString(cp.stdout); + } + + if (cp.stderr) { + stderrPromise = streamToString(cp.stderr); + } + + Promise.all([stdoutPromise, stderrPromise]).then((results) => { + const args = { + cp, + stdout: results[0], + stderr: results[1], + cmd, + exitCode, + duration: res.diff, + durationFormatted: res.diffFormatted, + }; + me.fire('command:exit', args); + resolve(args); + }); + + }); + + cp.on('error', (err) => { + err = err || { }; + const res = timer.stop(); + err.duration = res.diff; + err.durationFormatted = res.diffFormatted; + me.fire('command:error', err); + reject(err); + }); + }); + + commandPromise.cp = cp; + + return commandPromise; + }, + getKillCommand() { + return `kill -9 ${ commands.map(cmd => cmd.pid).join(' ')}`; + }, + stopAll() { + const me = this; + commands.forEach((cp) => { + if (!cp.exitCode) { + + cp.removeAllListeners('exit'); + cp.removeAllListeners('error'); + me.fire('command:killed', cp); + cp.kill('SIGINT'); + } + }); + }, + }); + }, +}; diff --git a/source/src/main.js b/source/src/main.js new file mode 100644 index 0000000..1620c96 --- /dev/null +++ b/source/src/main.js @@ -0,0 +1,129 @@ +const exec = require('child_process').exec; +const path = require('path'); +const nodeProcess = require('./process'); +const domain = require('domain'); +const manager = require('../index'); + +const addNPMBinToPath = cb => + new Promise((resolve, reject) => { + exec('npm bin', (error, stdout) => { + if (error) { + cb && cb(error); + reject(error); + return; + } + + if (typeof process.env.FORCE_COLOR === 'undefined') { + process.env.FORCE_COLOR = 'true'; + } + + process.env.PATH += `${ path.delimiter }${stdout.trim()}`; + + cb && cb(); + resolve(); + }); + }); + +const printFailed = entries => + entries.reduce((seq, entry) => { + seq += ` - cmd: ${ entry.cmd }, exitCode: ${ entry.exitCode }\n`; + return seq; + }, '\n'); + +module.exports = { + _execute(program, cmds) { + const cmdManager = manager.create(); // eslint-disable-line + + const opts = program.opts; + + cmdManager.on('command:start', (e, args) => { + program.subtle('starting command', args.cmd); + }); + + cmdManager.on('command:error', (e, args) => { + program.subtle('command error', args, 'duration: ', args.durationFormatted); + }); + + cmdManager.on('command:exit', (e, args) => { + const method = args.exitCode === 0 ? 'subtle' : 'warn'; + program[method]('command', args.cmd, 'exited with code', `${args.exitCode }, took:`, args.durationFormatted); + if (opts.sortOutput) { + args.stdout && console.log(args.stdout); + args.stderr && console.error(args.stderr); + } + if (args.exitCode !== 0 && opts.bail) { + program.warn('command', args.cmd, 'failed. Stopping all'); + cmdManager.stopAll(); + process.exit( 1 ); // eslint-disable-line + } + + }); + + cmdManager.on('command:killed', (e, args) => { + program.subtle('command killed:', args.cmd, 'pid:', args.pid); + }); + + const d = domain.create(); + + d.on('error', () => { + cmdManager.stopAll(); + }); + + const p = cmdManager.runCmds(cmds, { + stdio: opts.sortOutput ? 'pipe' : 'inherit', + }); + + p.then((args) => { + const results = []; + args.forEach((result) => { + if (result.exitCode !== 0) { + results.push({ + cmd: result.cmd, + exitCode: result.exitCode, + }); + } + }); + + if (results.length > 0) { + program.error('Some commands failed', '\n', printFailed(results)); + process.exit( 1 ); // eslint-disable-line + } + }); + + const lines = ` + Commands execution started + + To kill commands from the shell In case I become a zombie, execute: + + ${ cmdManager.getKillCommand()} + + `; + + program.subtle(lines); + + nodeProcess.on('SIGINT', (code) => { + program.subtle('killing all processes'); + cmdManager.stopAll(); + nodeProcess.exit(code); + }); + }, + async run(program) { + + const cmds = program.opts._; + + if (cmds.length === 0) { + program.error('please provide some commands to execute'); + program.showHelp(); + return; + } + + try { + await addNPMBinToPath(); + } catch ({ error, stderr }) { + program.error('received error', error); + stderr && program.error(stderr); + } + + this._execute(program, cmds); + }, +}; diff --git a/src/options.js b/source/src/options.js similarity index 64% rename from src/options.js rename to source/src/options.js index 92026fa..b72542b 100644 --- a/src/options.js +++ b/source/src/options.js @@ -1,26 +1,26 @@ -var path = require( 'path' ); +const path = require('path'); module.exports = { - pkgJSONPath: path.resolve( __dirname, '../package.json' ), - //useDefaultOptions: true, + pkgJSONPath: path.resolve(__dirname, '../package.json'), + // useDefaultOptions: true, optionator: { prepend: 'Usage: shell-exec [options] cmd1, cmd2, ... cmdn', options: [ { - heading: 'Options' + heading: 'Options', }, { option: 'bail', alias: 'b', type: 'Boolean', - description: 'Stop execution as soon as one of the task exit with an exit code different than 0 or an error happened' + description: 'Stop execution as soon as one of the task exit with an exit code different than 0 or an error happened', }, { option: 'sortOutput', alias: 'o', type: 'Boolean', - description: 'Sort the stdout and stderr output from the commands' - } - ] - } + description: 'Sort the stdout and stderr output from the commands', + }, + ], + }, }; diff --git a/src/process.js b/source/src/process.js similarity index 100% rename from src/process.js rename to source/src/process.js diff --git a/source/time-manager.js b/source/time-manager.js new file mode 100644 index 0000000..c220e79 --- /dev/null +++ b/source/time-manager.js @@ -0,0 +1,20 @@ +const pretty = require('pretty-time'); + +const timeManager = { + start() { + const start = process.hrtime(); + return { + stop() { + const diff = process.hrtime(start); + const theDiff = (diff[0] * 1e9) + diff[1]; + + return { + diff: theDiff, + diffFormatted: pretty(theDiff, 'ms'), + }; + }, + }; + }, +}; + +module.exports = timeManager; diff --git a/specs/index.js b/specs/index.js deleted file mode 100644 index a5c30b2..0000000 --- a/specs/index.js +++ /dev/null @@ -1,44 +0,0 @@ -describe( 'index', function () { - // var proxyquire = require( 'proxyquire' ); - - describe( 'create', function () { - it( 'should return an instance of a cmdManager', function () { - var cmdManager = require( '../index' ); - var cmdInstance = cmdManager.create(); - expect( cmdInstance.runCmds ).to.be.a( 'function' ); - expect( cmdInstance.run ).to.be.a( 'function' ); - expect( cmdInstance.getKillCommand ).to.be.a( 'function' ); - expect( cmdInstance.stopAll ).to.be.a( 'function' ); - } ); - } ); - - describe( 'runCmds', function () { - it( 'should execute all the passed commands', function () { - var cmdManager = require( '../index' ); - var me = this; - var cmdInstance = cmdManager.create(); - - var spy = cmdInstance.run = me.sandbox.spy(); - - cmdInstance.runCmds( [ - 'echo "hello"', - 'echo "world"', - 'echo "test"' - ] ); - - expect( spy.callCount ).to.equal( 3 ); - - var calls = spy.getCalls().map( function ( call ) { - return call.args[ 0 ]; - } ); - - expect( calls ).to.deep.equal( [ - 'echo "hello"', - 'echo "world"', - 'echo "test"' - ] ); - - } ); - } ); - -} ); diff --git a/specs/index.spec.js b/specs/index.spec.js new file mode 100644 index 0000000..cfaee6c --- /dev/null +++ b/specs/index.spec.js @@ -0,0 +1,42 @@ +const cmdManager = require('../source/index'); + +describe('index', () => { + // var proxyquire = require( 'proxyquire' ); + + describe('create', () => { + it('should return an instance of a cmdManager', () => { + const cmdInstance = cmdManager.create(); + expect(cmdInstance.runCmds).to.be.a('function'); + expect(cmdInstance.run).to.be.a('function'); + expect(cmdInstance.getKillCommand).to.be.a('function'); + expect(cmdInstance.stopAll).to.be.a('function'); + }); + }); + + describe('runCmds', () => { + it('should execute all the passed commands', function test() { + const me = this; + const cmdInstance = cmdManager.create(); + + const spy = cmdInstance.run = me.sandbox.spy(); + + cmdInstance.runCmds([ + 'echo "hello"', + 'echo "world"', + 'echo "test"', + ]); + + expect(spy.callCount).to.equal(3); + + const calls = spy.getCalls().map(call => call.args[0]); + + expect(calls).to.deep.equal([ + 'echo "hello"', + 'echo "world"', + 'echo "test"', + ]); + + }); + }); + +}); diff --git a/src/main.js b/src/main.js deleted file mode 100644 index a5dc6e5..0000000 --- a/src/main.js +++ /dev/null @@ -1,122 +0,0 @@ -var exec = require( 'child_process' ).exec; -var path = require( 'path' ); -var nodeProcess = require( './process' ); - -var printFailed = function ( entries ) { - return entries.reduce( function ( seq, entry ) { - seq += ' - cmd: ' + entry.cmd + ', exitCode: ' + entry.exitCode + '\n'; - return seq; - }, '\n' ); -}; - -module.exports = { - _execute: function ( program, cmds ) { - var cmdManager = require( '../index' ).create(); - - var opts = program.opts; - - cmdManager.on( 'command:start', function ( e, args ) { - program.subtle( 'starting command', args.cmd ); - } ); - - cmdManager.on( 'command:error', function ( e, args ) { - program.subtle( 'command error', args, 'duration: ', args.durationFormatted ); - } ); - - cmdManager.on( 'command:exit', function ( e, args ) { - var method = args.exitCode === 0 ? 'subtle' : 'warn'; - program[ method ]( 'command', args.cmd, 'exited with code', args.exitCode + ', took:', args.durationFormatted ); - if ( opts.sortOutput ) { - args.stdout && console.log( args.stdout ); - args.stderr && console.error( args.stderr ); - } - if ( args.exitCode !== 0 && opts.bail ) { - program.warn( 'command', args.cmd, 'failed. Stopping all' ); - cmdManager.stopAll(); - process.exit( 1 ); // eslint-disable-line - } - - } ); - - cmdManager.on( 'command:killed', function ( e, args ) { - program.subtle( 'command killed:', args.cmd, 'pid:', args.pid ); - } ); - - var d = require( 'domain' ).create(); - - d.on( 'error', function () { - cmdManager.stopAll(); - } ); - - var p = cmdManager.runCmds( cmds, { - stdio: opts.sortOutput ? 'pipe' : 'inherit' - } ); - - p.then( function ( args ) { - var results = [ ]; - args.forEach( function ( result ) { - if ( result.exitCode !== 0 ) { - results.push( { - cmd: result.cmd, - exitCode: result.exitCode - } ); - } - } ); - - if ( results.length > 0 ) { - program.error( 'Some commands failed', '\n', printFailed( results ) ); - process.exit( 1 ); // eslint-disable-line - } - } ); - - var lines = [ - 'Commands execution started', - '', - ' To kill commands from the shell In case I become a zombie, execute:', - '', - ' ' + cmdManager.getKillCommand(), - '', - '' - ]; - - program.subtle( lines.join( '\n' ) ); - - nodeProcess.on( 'SIGINT', function ( code ) { - program.subtle( 'killing all processes' ); - cmdManager.stopAll(); - nodeProcess.exit( code ); - } ); - }, - run: function ( program ) { - - var cmds = program.opts._; - - var addNPMBinToPath = function ( cb ) { - exec( 'npm bin', function ( error, stdout, stderr ) { - if ( error ) { - program.error( 'received error', error ); - stderr && program.error( stderr ); - return; - } - - if ( typeof process.env.FORCE_COLOR === 'undefined' ) { - process.env.FORCE_COLOR = 'true'; - } - - process.env.PATH += '' + path.delimiter + stdout.trim(); - - cb && cb(); - } ); - }; - - if ( cmds.length === 0 ) { - program.error( 'please provide some commands to execute' ); - program.showHelp(); - return; - } - var me = this; - addNPMBinToPath( function () { - me._execute( program, cmds ); - } ); - } -}; diff --git a/time-manager.js b/time-manager.js index 83c24a7..2d261e9 100644 --- a/time-manager.js +++ b/time-manager.js @@ -1,20 +1 @@ -var pretty = require( 'pretty-time' ); - -var timeManager = { - start: function () { - var start = process.hrtime(); - return { - stop: function () { - var diff = process.hrtime( start ); - var theDiff = (diff[ 0 ] * 1e9) + diff[ 1 ]; - - return { - diff: theDiff, - diffFormatted: pretty(theDiff, 'ms') - }; - } - }; - } -}; - -module.exports = timeManager; \ No newline at end of file +module.exports = require('./dist/time-manager');