├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── index.js ├── package.json ├── renovate.json └── tests ├── basic.js ├── cd.js ├── command-name.js ├── count.js ├── error.js ├── events.js ├── kill.js ├── output-file.js └── stdout.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea/ 3 | .ipr 4 | .iws 5 | *~ 6 | ~* 7 | *.diff 8 | *.patch 9 | *.bak 10 | .DS_Store 11 | Thumbs.db 12 | .project 13 | .*proj 14 | .svn/ 15 | *.swp 16 | *.swo 17 | *.log 18 | *.sublime-project 19 | *.sublime-workspace 20 | node_modules/ 21 | dist/ 22 | tmp/ 23 | .spm-build 24 | .buildpath 25 | .settings 26 | .yml 27 | _site 28 | example.js 29 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "4" 5 | - "5" 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This software is released under the MIT license: 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # exeq 2 | 3 | Execute shell commands in queue. 4 | 5 | [![NPM version](https://img.shields.io/npm/v/exeq.svg?style=flat)](https://npmjs.org/package/exeq) 6 | [![Build Status](https://img.shields.io/travis/afc163/exeq.svg?style=flat)](https://travis-ci.org/afc163/exeq) 7 | [![David Status](https://img.shields.io/david/afc163/exeq.svg?style=flat)](https://david-dm.org/afc163/exeq) 8 | [![NPM downloads](http://img.shields.io/npm/dm/exeq.svg?style=flat)](https://npmjs.org/package/exeq) 9 | 10 | --- 11 | 12 | ## Install 13 | 14 | ```bash 15 | $ npm install exeq --save 16 | ``` 17 | 18 | ## Usage 19 | 20 | ### exeq() 21 | 22 | ```js 23 | exeq( 24 | 'mkdir example', 25 | 'rm -rf example' 26 | ); 27 | ``` 28 | 29 | ### Promise `2.0.0+` 30 | 31 | ```js 32 | // promise 33 | exeq( 34 | 'mkdir example', 35 | 'cd example', 36 | 'touch README.md', 37 | 'touch somefile', 38 | 'rm somefile', 39 | 'ls -l', 40 | 'cd ..', 41 | 'rm -rf example', 42 | 'ls -l > output.txt' 43 | ).then(function() { 44 | console.log('done!'); 45 | }).catch(function(err) { 46 | console.log(err); 47 | }); 48 | ``` 49 | 50 | ### Array 51 | 52 | ```js 53 | exeq([ 54 | 'mkdir example', 55 | 'rm -rf example' 56 | ]); 57 | ``` 58 | 59 | ### stdout & stderr 60 | 61 | ```js 62 | exeq( 63 | 'echo 123', 64 | 'echo 456', 65 | 'echo 789' 66 | ).then(function(results) { 67 | console.log(results[0].stdout); // '123' 68 | console.log(results[1].stdout); // '456' 69 | console.log(results[2].stdout); // '789' 70 | }); 71 | ``` 72 | 73 | ```js 74 | exeq( 75 | 'not-existed-command' 76 | ).then(function(results) { 77 | }).catch(function(err) { 78 | console.log(err); // { code: '127', stderr: ' ... ' } 79 | }); 80 | ``` 81 | 82 | ### change cwd 83 | 84 | ```js 85 | // cd command would change spawn cwd automatically 86 | // create README.md in example 87 | exeq( 88 | 'mkdir example', 89 | 'cd example', 90 | 'touch README.md' 91 | ); 92 | ``` 93 | 94 | ### Kill the execution 95 | 96 | ```js 97 | var proc = exeq([ 98 | 'echo 1', 99 | 'sleep 10', 100 | 'echo 2' 101 | ]); 102 | proc.q.kill(); 103 | ``` 104 | 105 | ### Events 106 | 107 | ```js 108 | var proc = exeq([ 109 | 'echo 1', 110 | 'echo 2' 111 | ]); 112 | 113 | proc.q.on('stdout', function(data) { 114 | console.log(data); 115 | }); 116 | 117 | proc.q.on('stderr', function(data) { 118 | console.log(data); 119 | }); 120 | 121 | proc.q.on('killed', function(reason) { 122 | console.log(reason); 123 | }); 124 | 125 | proc.q.on('done', function() { 126 | }); 127 | 128 | proc.q.on('failed', function() { 129 | }); 130 | ``` 131 | 132 | ## Test 133 | 134 | ```bash 135 | $ npm test 136 | ``` 137 | 138 | ## License 139 | 140 | The MIT License (MIT) 141 | 142 | ## Support on Beerpay 143 | Hey dude! Help me out for a couple of :beers:! 144 | 145 | [![Beerpay](https://beerpay.io/afc163/exeq/badge.svg?style=beer-square)](https://beerpay.io/afc163/exeq) [![Beerpay](https://beerpay.io/afc163/exeq/make-wish.svg?style=flat-square)](https://beerpay.io/afc163/exeq?focus=wish) 146 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var inherits = require('util').inherits; 3 | var spawn = require('child_process').spawn; 4 | var path = require('path'); 5 | var platform = require('os').platform(); 6 | 7 | function Exeq(commands) { 8 | EventEmitter.call(this); 9 | this.commands = commands || []; 10 | this.cwd = ''; 11 | this.results = []; 12 | 13 | var instance = new Promise(this.run.bind(this)); 14 | instance.q = this; 15 | 16 | return instance; 17 | } 18 | 19 | inherits(Exeq, EventEmitter); 20 | 21 | Exeq.prototype.run = function(resolve, reject) { 22 | 23 | var that = this; 24 | 25 | var stdout = new Buffer(''); 26 | var stderr = new Buffer(''); 27 | 28 | // done! 29 | if (this.commands.length === 0) { 30 | this.emit('done', this.results); 31 | resolve(this.results); 32 | return; 33 | } 34 | 35 | var cmdString = this.commands.shift(); 36 | var parsed = parseCommand(cmdString); 37 | var s = this.proc = spawn(parsed.cmd, parsed.args, { 38 | cwd: this.cwd 39 | }); 40 | 41 | s.stdout.pipe(process.stdout); 42 | s.stdout.on('data', function(data) { 43 | that.emit('stdout', data); 44 | stdout += data.toString(); 45 | }); 46 | 47 | s.stderr.pipe(process.stderr); 48 | s.stderr.on('data', function(data) { 49 | that.emit('stderr', data); 50 | stderr += data.toString(); 51 | }); 52 | 53 | s.on('close', function(code, signal) { 54 | var reason; 55 | if (code) { 56 | reason = { 57 | code: code, 58 | stdout: stdout.toString(), 59 | stderr: stderr.toString() 60 | }; 61 | 62 | that.emit('failed', reason); 63 | return reject(reason); 64 | } 65 | 66 | that.results.push({ 67 | cmd: cmdString, 68 | stdout: stdout.toString(), 69 | stderr: stderr.toString() 70 | }); 71 | 72 | if (that.killed) { 73 | reason = { 74 | code: code, 75 | stderr: that.results.map(function(result) { 76 | return result.stdout.toString(); 77 | }).join('') + 'Process has been killed.' 78 | }; 79 | 80 | if (signal) { 81 | reason.errno = signal; 82 | } 83 | 84 | that.emit('killed', reason); 85 | return reject(reason); 86 | } 87 | 88 | // cd /path/to 89 | // change cwd to /path/to 90 | if (parsed.changeCwd) { 91 | that.cwd = path.resolve(that.cwd, parsed.changeCwd); 92 | } 93 | that.run(resolve, reject); 94 | }); 95 | }; 96 | 97 | Exeq.prototype.kill = function() { 98 | if (this.proc) { 99 | try { 100 | this.proc.kill('SIGTERM'); 101 | this.killed = true; 102 | } catch (e) { 103 | if (e.errno != 'ESRCH') { 104 | throw (e); 105 | } 106 | } 107 | } 108 | }; 109 | 110 | 111 | module.exports = function() { 112 | var cmds = [], args = Array.prototype.slice.call(arguments); 113 | args.forEach(function(arg) { 114 | if (Array.isArray(arg)) { 115 | cmds = cmds.concat(arg); 116 | } else { 117 | cmds.push(arg); 118 | } 119 | }); 120 | return new Exeq(cmds); 121 | }; 122 | 123 | function parseCommand(cmd) { 124 | cmd = cmd.trim(); 125 | var command = (platform === 'win32' ? 'cmd.exe' : 'sh'); 126 | var args = (platform === 'win32' ? ['/s', '/c'] : ['-c']); 127 | var changeCwd; 128 | // change cwd for "cd /path/to" 129 | if (/^cd\s+/.test(cmd)) { 130 | changeCwd = cmd.replace(/^cd\s+/, ''); 131 | } 132 | return { 133 | cmd: command, 134 | args: args.concat([cmd]), 135 | changeCwd: changeCwd 136 | }; 137 | } 138 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "exeq", 3 | "version": "3.0.0", 4 | "description": "Excute shell commands in queue", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git@github.com:afc163/exeq.git" 9 | }, 10 | "author": "afc163 ", 11 | "keywords": [ 12 | "spawn", 13 | "execute", 14 | "exec", 15 | "command", 16 | "shell", 17 | "promise", 18 | "synchronously", 19 | "bash", 20 | "kill" 21 | ], 22 | "engines": { 23 | "node": ">= 0.10.0" 24 | }, 25 | "license": "MIT", 26 | "devDependencies": { 27 | "is-promise": "~1.0.1", 28 | "tape": "~4.0.0" 29 | }, 30 | "scripts": { 31 | "test": "tape tests/*.js" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /tests/basic.js: -------------------------------------------------------------------------------- 1 | var test = require('tape').test; 2 | var exeq = require('..'); 3 | var isPromise = require('is-promise'); 4 | 5 | test('basic use', function(t) { 6 | 7 | t.equal(typeof exeq, 'function'); 8 | t.equal(isPromise(exeq()), true); 9 | 10 | t.end(); 11 | 12 | }); 13 | 14 | -------------------------------------------------------------------------------- /tests/cd.js: -------------------------------------------------------------------------------- 1 | var test = require('tape').test; 2 | var exeq = require('..'); 3 | 4 | test('cd change cwd', function(t) { 5 | 6 | var tempDirName = '__temp-for-tests'; 7 | 8 | exeq( 9 | 'mkdir ' + tempDirName, 10 | 'cd ' + tempDirName, 11 | 'pwd', 12 | 'cd ..', 13 | 'pwd', 14 | 'rm -rf ' + tempDirName 15 | ).then(function(results) { 16 | t.ok(results[2].stdout.indexOf(tempDirName) > -1); 17 | t.notOk(results[4].stdout.indexOf(tempDirName) > -1); 18 | t.end(); 19 | }); 20 | 21 | }); 22 | 23 | -------------------------------------------------------------------------------- /tests/command-name.js: -------------------------------------------------------------------------------- 1 | var test = require('tape').test; 2 | var exeq = require('..'); 3 | 4 | test('command name', function(t) { 5 | exeq( 6 | [ 7 | 'ls -l', 8 | 'cd ..' 9 | ], 10 | 'ps' 11 | ).then(function(results) { 12 | t.equal(results[0].cmd, 'ls -l'); 13 | t.equal(results[1].cmd, 'cd ..'); 14 | t.equal(results[2].cmd, 'ps'); 15 | t.end(); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /tests/count.js: -------------------------------------------------------------------------------- 1 | var test = require('tape').test; 2 | var exeq = require('..'); 3 | 4 | test('command count', function(t) { 5 | 6 | exeq( 7 | 'cd /usr/bin', 8 | 'cd ..', 9 | 'cd /usr/bin' 10 | ).then(function(results) { 11 | t.equal(results.length, 3); 12 | return exeq(); 13 | }).then(function(results) { 14 | t.equal(results.length, 0); 15 | t.end(); 16 | }); 17 | 18 | }); 19 | -------------------------------------------------------------------------------- /tests/error.js: -------------------------------------------------------------------------------- 1 | var test = require('tape').test; 2 | var exeq = require('..'); 3 | 4 | test('catch error', function(t) { 5 | 6 | exeq('not-existed-cmd').then(function(results) { 7 | }).catch(function(err) { 8 | t.equal(err.code, 127); 9 | t.ok(err.stderr.indexOf('not found') > -1); 10 | t.end(); 11 | }); 12 | 13 | }); 14 | 15 | 16 | -------------------------------------------------------------------------------- /tests/events.js: -------------------------------------------------------------------------------- 1 | var test = require('tape').test; 2 | var exeq = require('..'); 3 | 4 | test('event done', function(t) { 5 | 6 | // Keep the origin promise instance 7 | var proc = exeq([ 8 | 'echo 1', 9 | 'echo 2' 10 | ]); 11 | 12 | var stdout = []; 13 | 14 | proc.q.on('stdout', function(data) { 15 | stdout.push(data); 16 | }); 17 | 18 | proc.q.on('done', function() { 19 | t.equal(stdout.join(''), '1\n2\n'); 20 | t.end(); 21 | }); 22 | 23 | }); 24 | 25 | test('event fail', function(t) { 26 | 27 | var proc = exeq([ 28 | 'fail-me' 29 | ]); 30 | 31 | var stderr = []; 32 | 33 | proc.catch(function(){}); 34 | 35 | proc.q.on('stderr', function(data) { 36 | stderr.push(data); 37 | }); 38 | 39 | proc.q.on('failed', function() { 40 | t.ok(stderr.join('').indexOf('not found') > -1 ); 41 | t.end(); 42 | }); 43 | }); 44 | 45 | test('event killed', function(t) { 46 | var proc = exeq([ 47 | 'echo 1', 48 | 'sleep 10', 49 | 'echo 2' 50 | ]); 51 | 52 | var stdout = []; 53 | 54 | 55 | proc.q.on('stdout', function(data) { 56 | stdout.push(data); 57 | }); 58 | 59 | proc.q.on('killed', function(data) { 60 | t.equal(stdout.join(''), '1\n'); 61 | t.equal(data.stderr, '1\nProcess has been killed.'); 62 | t.end(); 63 | }); 64 | 65 | proc.catch(function(){}); 66 | 67 | setTimeout(function(){ 68 | proc.q.kill(); 69 | }, 600); 70 | }); 71 | -------------------------------------------------------------------------------- /tests/kill.js: -------------------------------------------------------------------------------- 1 | var test = require('tape').test; 2 | var exeq = require('..'); 3 | 4 | test('kill process 1', function(t) { 5 | 6 | // Keep the origin promise instance 7 | var proc = exeq([ 8 | 'echo 1', 9 | 'sleep 10', 10 | 'echo 2' 11 | ]); 12 | 13 | proc.catch(function(err) { 14 | t.equal(err.stderr, '1\nProcess has been killed.'); 15 | t.end(); 16 | }); 17 | 18 | setTimeout(function(){ 19 | proc.q.kill(); 20 | }, 300); 21 | 22 | }); 23 | 24 | test('kill process 2', function(t) { 25 | 26 | var proc = exeq([ 27 | 'sleep 10', 28 | 'echo 1', 29 | 'echo 2' 30 | ]); 31 | 32 | proc.catch(function(err) { 33 | t.equal(err.errno, 'SIGTERM'); 34 | t.equal(err.stderr, 'Process has been killed.'); 35 | t.end(); 36 | }); 37 | 38 | setTimeout(function(){ 39 | proc.q.kill(); 40 | }, 300); 41 | 42 | }); 43 | -------------------------------------------------------------------------------- /tests/output-file.js: -------------------------------------------------------------------------------- 1 | var test = require('tape').test; 2 | var exeq = require('..'); 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | 6 | test('output file', function(t) { 7 | 8 | var n = -1; 9 | 10 | exeq( 11 | 'ls > a.txt' 12 | ).then(function(results) { 13 | t.ok(fs.existsSync(path.resolve('a.txt'))); 14 | t.ok(fs.readFileSync('a.txt').toString().indexOf('a.txt') >= 0); 15 | return exeq('rm a.txt'); 16 | }).then(function() { 17 | t.notOk(fs.existsSync('a.txt')); 18 | t.end(); 19 | }); 20 | 21 | }); 22 | -------------------------------------------------------------------------------- /tests/stdout.js: -------------------------------------------------------------------------------- 1 | var test = require('tape').test; 2 | var exeq = require('..'); 3 | 4 | test('stdout', function(t) { 5 | 6 | exeq( 7 | 'echo 123', 8 | 'echo "string"', 9 | 'echo 456', 10 | 'date' 11 | ).then(function(results) { 12 | t.equal(results[0].stdout.trim(), '123'); 13 | t.equal(results[1].stdout.trim(), 'string'); 14 | t.equal(results[2].stdout.trim(), '456'); 15 | var date = new Date(results[3].stdout.trim()); 16 | t.notEqual(date.toString(), 'Invalid Date'); 17 | t.end(); 18 | }); 19 | 20 | }); 21 | 22 | 23 | --------------------------------------------------------------------------------