├── .gitignore ├── lib └── index.js ├── package.json └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 2 | var path = require('path'); 3 | var events = require('events'); 4 | var cp = require('child_process'); 5 | var merge = require('merge-recursive'); 6 | var which = require('which').sync; 7 | 8 | var PATH_SEP = process.platform.match(/^win/) ? ';' : ':'; 9 | 10 | exports.open = function(dir) { 11 | return new CommandRunner(dir); 12 | }; 13 | 14 | exports.writeTo = function(stream) { 15 | return function(data) { 16 | stream.write(data); 17 | }; 18 | }; 19 | 20 | var CommandRunner = exports.CommandRunner = function(dir, env) { 21 | this.cwd = dir || process.cwd(); 22 | this.env = env || process.env; 23 | 24 | // Add the node_modules/.bin directory to the PATH, unless it is defined manually 25 | if (! (env && env.PATH)) { 26 | this.env.PATH = this.env.PATH || process.env.PATH; 27 | this.env.PATH = path.join('node_modules', '.bin') + PATH_SEP + this.env.PATH; 28 | } 29 | 30 | this.queue = [ ]; 31 | this.running = false; 32 | this.stopped = false; // TODO 33 | 34 | this.lastResult = null; 35 | }; 36 | 37 | CommandRunner.prototype = new events.EventEmitter(); 38 | 39 | CommandRunner.prototype.exec = function(cmd, args, opts) { 40 | this.queue.push({ 41 | type: 'exec', 42 | cmd: cmd, 43 | args: args, 44 | opts: opts 45 | }); 46 | return this._startQueue(); 47 | }; 48 | 49 | CommandRunner.prototype.chdir = function(dir) { 50 | this.queue.push({ type: 'chdir', dir: dir }); 51 | return this._startQueue(); 52 | }; 53 | 54 | CommandRunner.prototype.then = function(callback) { 55 | this.queue.push({ type: 'callback', func: callback }); 56 | return this._startQueue(); 57 | }; 58 | 59 | CommandRunner.prototype._startQueue = function() { 60 | process.nextTick(this._next.bind(this)); 61 | return this; 62 | }; 63 | 64 | CommandRunner.prototype._next = function() { 65 | if (this.queue.length) { 66 | if (! this.running) { 67 | this.running = true; 68 | this._run(this.queue.shift(), this._next.bind(this)); 69 | } 70 | } else { 71 | this.running = false; 72 | } 73 | }; 74 | 75 | CommandRunner.prototype._run = function(action, callback) { 76 | callback = async(callback); 77 | 78 | var done = function() { 79 | this.running = false; 80 | callback(); 81 | }.bind(this); 82 | 83 | switch (action.type) { 84 | // Run a callback 85 | case 'callback': 86 | // If function takes a callback, run async 87 | if (action.func.length) { 88 | action.func.call(this, done); 89 | } 90 | // Otherwise, run sync 91 | else { 92 | action.func.call(this); 93 | done(); 94 | } 95 | break; 96 | 97 | // Change the current working directory 98 | case 'chdir': 99 | this.dir = path.resolve(this.dir, action.dir); 100 | done(); 101 | break; 102 | 103 | // Run a proc.spawn 104 | case 'exec': 105 | action.opts = merge.recursive( 106 | {env: { }}, 107 | {env: this.env, cwd: this.cwd}, 108 | action.opts || { } 109 | ); 110 | 111 | var proc = cp.spawn(which(action.cmd), action.args || [ ], action.opts); 112 | 113 | this.lastPid = proc.pid; 114 | 115 | var output = ''; 116 | var stdout = ''; 117 | var stderr = ''; 118 | 119 | proc.stdout.on('data', function(data) { 120 | stdout += String(data); 121 | output += String(data); 122 | this.emit('stdout', data); 123 | }.bind(this)); 124 | 125 | proc.stderr.on('data', function(data) { 126 | stderr += String(data); 127 | output += String(data); 128 | this.emit('stderr', data); 129 | }.bind(this)); 130 | 131 | proc.on('exit', function(code) { 132 | this.lastOutput = { 133 | stdout: stdout, 134 | stderr: stderr, 135 | output: output 136 | }; 137 | this.emit('exit', code); 138 | done(); 139 | }.bind(this)); 140 | break; 141 | } 142 | }; 143 | 144 | CommandRunner.prototype._emit = function(event) { 145 | return function() { 146 | var args = slice(arguments); 147 | args.shift(event); 148 | this.emit.apply(this, event); 149 | }.bind(this); 150 | }; 151 | 152 | // ------------------------------------------------------------------ 153 | 154 | function slice(arr) { 155 | return Array.prototype.slice.call(arr); 156 | } 157 | 158 | function async(func) { 159 | return function() { 160 | process.nextTick(func); 161 | }; 162 | } 163 | 164 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "command", 3 | "version": "0.0.5", 4 | "description": "A chainable, promise-based utility for running commands with child_process.spawn", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/UmbraEngineering/command" 12 | }, 13 | "keywords": [ 14 | "command", 15 | "cli", 16 | "spawn", 17 | "child", 18 | "process" 19 | ], 20 | "author": "James Brumond", 21 | "license": "MIT", 22 | "dependencies": { 23 | "merge-recursive": "0.0.3", 24 | "which": "1.0.5" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # command 2 | 3 | A Node.js chainable, promise-based utility for running commands with child_process.spawn 4 | 5 | ## Install 6 | 7 | ```bash 8 | $ npm install command 9 | ``` 10 | 11 | ## Usage 12 | 13 | ```javascript 14 | var command = require('command'); 15 | 16 | command.open('/some/directory/path') 17 | .on('stdout', command.writeTo(process.stdout)) 18 | .on('stderr', command.writeTo(process.stderr)) 19 | .chdir('..') 20 | .exec('ls') 21 | .then(function() { 22 | var stdout = this.lastOutput.stdout; 23 | if (! stdout.trim().length) { 24 | console.warn('No files found!'); 25 | } 26 | }); 27 | ``` 28 | 29 | --------------------------------------------------------------------------------