From 09a7158c8b2a4affe3844cf1873916f1e8154745 Mon Sep 17 00:00:00 2001 From: Peter Sieg Date: Fri, 15 May 2015 18:31:32 -0400 Subject: [PATCH 01/10] Add a spawn method for logging stdout and stderr. --- lib/drush-node.js | 81 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/lib/drush-node.js b/lib/drush-node.js index 886ae3c..ec43778 100644 --- a/lib/drush-node.js +++ b/lib/drush-node.js @@ -1,5 +1,7 @@ +/* globals require, module, console */ var _ = require('lodash'); var exec = require('child_process').exec; +var spawn = require('child_process').spawn; var Deferred = require('promised-io/promise').Deferred; var Drush = { @@ -104,5 +106,84 @@ Drush.exec = function (command, options) { return def.promise; }; +/** + * Spawn a drush command. + * + * @param {array} args + * An array of arguments to append to the command, e.g. ['cc', 'drush']. + * @param {object} options + * A hash of options to add to the command, can contain: + * - alias: the drush alias, e.g. "@self" to execute the command with. + * - simulate: boolean, simulates all relevant actions. + * - uri: the URI of the drupal site to use. + * - echo: text to echo to the drush command. + * - cat: a file to cat to the drush command. + * + * @return Promise + */ +Drush.spawn = function (args, options) { + args = args || []; + options = options || {}; + var def = new Deferred(); + var pipeToDrush = function (command, pipeArgs) { + var cmd = spawn(command, pipeArgs); + cmd.stdout.on('data', function (data) { + cmd.stdin.write(data); + }); + cmd.stderr.on('data', function (data) { + console.log('' + data); + }); + cmd.on('close', function (code) { + if (code !== 0) { + return def.reject(command + ' process exited with code ' + code); + } + cmd.stdin.end(); + }); + }; + + // Prepend the alias argument. + if (options.alias) { + args = [options.alias].concat(args); + } + + // Add simulate flag. + if (options.simulate) { + args.push('-s'); + } + + // Add uri arguments. + if (options.uri) { + args.push('-l'); + args.push(options.uri); + } + + // Add -y flag to prevent prompts from hanging. + args.push('-y'); + + // Handle echo and cat options. + if (options.echo) { + pipeToDrush('echo', [options.echo]); + } + else if (options.cat) { + pipeToDrush('cat', [options.cat]); + } + + var run = spawn(Drush.command, args); + run.stdout.on('data', function (data) { + console.log('' + data); + }); + run.stderr.on('data', function (data) { + console.log('' + data); + }); + run.on('close', function (code) { + if (code !== 0) { + return def.reject('drush process exited with code ' + code); + } + def.resolve(); + }); + + return def.promise; +}; + module.exports = Drush; From f5cfb2e1bce6b200675824f18b9b6f5f5155a650 Mon Sep 17 00:00:00 2001 From: Peter Sieg Date: Sat, 16 May 2015 16:02:21 -0400 Subject: [PATCH 02/10] Add test for spawn method. --- test/drush_test.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/drush_test.js b/test/drush_test.js index f0a08e4..2c06e45 100644 --- a/test/drush_test.js +++ b/test/drush_test.js @@ -47,6 +47,25 @@ exports['drush'] = { } ); }); + }, + + spawn: function (test) { + test.expect(1); + + drush.init() + .then(function () { + drush.spawn(['st']) + .then( + function (res) { + test.ok(true, 'Drush can execute.'); + test.done(); + }, + function (err) { + test.ok(false, 'Drush output unexpected.'); + test.done(); + } + ); + }); } }; From 28516cc2943ff470beeb5286e451205cf71e8de5 Mon Sep 17 00:00:00 2001 From: Peter Sieg Date: Mon, 18 May 2015 00:55:26 -0400 Subject: [PATCH 03/10] Use spawn for Drush.exec but only log output of log option is specified. --- lib/drush-node.js | 124 +++++++++++++++------------------------------- 1 file changed, 40 insertions(+), 84 deletions(-) diff --git a/lib/drush-node.js b/lib/drush-node.js index ec43778..3777f24 100644 --- a/lib/drush-node.js +++ b/lib/drush-node.js @@ -1,4 +1,3 @@ -/* globals require, module, console */ var _ = require('lodash'); var exec = require('child_process').exec; var spawn = require('child_process').spawn; @@ -6,7 +5,7 @@ var Deferred = require('promised-io/promise').Deferred; var Drush = { execOptions: { - maxBuffer: 256 * 1024 * 1024 + log: false } }; @@ -59,6 +58,7 @@ Drush.version = function () { * The drush command to execute. * @param {object} options * A hash of options to add to the command, can contain: + * - log: flag to log the output of the drush command. * - alias: the drush alias, e.g. "@self" to execute the command with. * - simulate: boolean, simulates all relevant actions. * - uri: the URI of the drupal site to use. @@ -69,77 +69,10 @@ Drush.version = function () { */ Drush.exec = function (command, options) { options = options || {}; + var args = command.split(' ') || ''; + var log = typeof options.log !== 'undefined' ? options.log : this.execOptions.log; var def = new Deferred(); - var cmd = Drush.command; - var prop = ''; - - if (options.alias) { - cmd += ' ' + options.alias; - } - - if (options.simulate) { - cmd += ' -s'; - } - - if (options.uri) { - cmd += ' -l ' + options.uri; - } - - cmd += ' ' + command + ' -y'; - - if (options.echo) { - cmd = 'echo ' + options.echo + ' | ' + cmd; - } - - if (options.cat) { - cmd = 'cat ' + options.cat + ' | ' + cmd; - } - - exec(cmd, this.execOptions, function (err, stdout, stderr) { - if (err) { - return def.reject(err); - } - - def.resolve(stdout); - }); - - return def.promise; -}; - -/** - * Spawn a drush command. - * - * @param {array} args - * An array of arguments to append to the command, e.g. ['cc', 'drush']. - * @param {object} options - * A hash of options to add to the command, can contain: - * - alias: the drush alias, e.g. "@self" to execute the command with. - * - simulate: boolean, simulates all relevant actions. - * - uri: the URI of the drupal site to use. - * - echo: text to echo to the drush command. - * - cat: a file to cat to the drush command. - * - * @return Promise - */ -Drush.spawn = function (args, options) { - args = args || []; - options = options || {}; - var def = new Deferred(); - var pipeToDrush = function (command, pipeArgs) { - var cmd = spawn(command, pipeArgs); - cmd.stdout.on('data', function (data) { - cmd.stdin.write(data); - }); - cmd.stderr.on('data', function (data) { - console.log('' + data); - }); - cmd.on('close', function (code) { - if (code !== 0) { - return def.reject(command + ' process exited with code ' + code); - } - cmd.stdin.end(); - }); - }; + var output = ''; // Prepend the alias argument. if (options.alias) { @@ -160,26 +93,49 @@ Drush.spawn = function (args, options) { // Add -y flag to prevent prompts from hanging. args.push('-y'); + // Initialize drush child process. + var drush = spawn(Drush.command, args); + // Handle echo and cat options. - if (options.echo) { - pipeToDrush('echo', [options.echo]); - } - else if (options.cat) { - pipeToDrush('cat', [options.cat]); + if (options.echo || options.cat) { + var pipeCommand = options.echo ? 'echo' : 'cat'; + var cmd = spawn(pipeCommand, options[pipeCommand].split(' ')); + + cmd.stdout.on('data', function (data) { + drush.stdin.write(data); + }); + cmd.stderr.on('data', function (data) { + if (log) { + console.log('' + data); + } + }); + cmd.on('close', function (code) { + if (code !== 0) { + return def.reject(pipeCommand + ' process exited with code ' + code); + } + drush.stdin.end(); + }); } - var run = spawn(Drush.command, args); - run.stdout.on('data', function (data) { - console.log('' + data); + // Listen to stdout and stderr streams and resolve the promise when the drush + // process closes. + drush.stdout.on('data', function (data) { + output += data; + if (log) { + console.log('' + data); + } }); - run.stderr.on('data', function (data) { - console.log('' + data); + drush.stderr.on('data', function (data) { + output += data; + if (log) { + console.log('' + data); + } }); - run.on('close', function (code) { + drush.on('close', function (code) { if (code !== 0) { return def.reject('drush process exited with code ' + code); } - def.resolve(); + def.resolve(output); }); return def.promise; From 96e0dcd0b9e6ef2abecec524d81f645916753d2e Mon Sep 17 00:00:00 2001 From: Peter Sieg Date: Mon, 18 May 2015 00:55:54 -0400 Subject: [PATCH 04/10] Remove old spawn test. --- test/drush_test.js | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/test/drush_test.js b/test/drush_test.js index 2c06e45..9266d59 100644 --- a/test/drush_test.js +++ b/test/drush_test.js @@ -47,25 +47,5 @@ exports['drush'] = { } ); }); - }, - - spawn: function (test) { - test.expect(1); - - drush.init() - .then(function () { - drush.spawn(['st']) - .then( - function (res) { - test.ok(true, 'Drush can execute.'); - test.done(); - }, - function (err) { - test.ok(false, 'Drush output unexpected.'); - test.done(); - } - ); - }); } }; - From e8a16bba31a929d36fc20914c7a89b3baf66326d Mon Sep 17 00:00:00 2001 From: Peter Sieg Date: Mon, 18 May 2015 01:10:53 -0400 Subject: [PATCH 05/10] Update the readme. --- README.md | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 16a33e3..c74d47f 100644 --- a/README.md +++ b/README.md @@ -9,44 +9,51 @@ var drush = require('drush-node'); drush.init().then( function () { + // Executes `drush updb` and logs the output after the command has completed. drush.exec('updb') .then( function (res) { console.log(res); } ); + + // Executes `drush cc all` and logs the output as the command is running. + drush.exec('cc all'], {log: true}); } ); ``` The module is built on top of [promised-io](https://github.com/kriszyp/promised-io) -and exec returns a promise object. Chaining commands can therefore be conveniently -done as follows: +and exec returns a promise object. Chaining commands can therefore be +conveniently done as follows: ```javascript var group = require('promised-io/promise').all([ - drush.init(), + drush.init({log: true}), drush.exec('updb'), drush.exec('fra'), drush.exec('cc all') ]); group.then(function (res) { - console.log(res.join("\r")); + console.log('All commands completed.'); }); ``` -You must call `Drush.init()` before executing other commands, but as long -as the `Drush` object remains in scope you only need to call it once. +You must call `drush.init()` before executing other commands, but as long +as the `drush` object remains in scope you only need to call it once. ### Advanced options -You may pass additional options to the underlaying [exec commands](http://nodejs.org/api/child_process.html#child_process_child_process_exec_command_options_callback) +You may pass additional options to the underlaying [spawn commands](https://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options) by calling init with a hash of appropriate options: ```javascript -drush.init({ maxBuffer: 256 * 1024 * 1024 }) +drush.init({ detached: true }) ``` -*Note*: You may need to increase the buffer size for commands that return a lot -of output. +*Note*: The `log` option is not an option for the spawn command, but can be +included when calling drush.init(). Specifying this option when calling +drush.init() prevents the need to specify it on every individual call to +drush.exec() in cases where you want all output for every command to be logged +to the terminal. From e88af722376ac84b132ef4e7e4b9877f00c1a3a8 Mon Sep 17 00:00:00 2001 From: Peter Sieg Date: Mon, 18 May 2015 01:25:40 -0400 Subject: [PATCH 06/10] Allow either a string or an array of arguments when calling exec. --- lib/drush-node.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/drush-node.js b/lib/drush-node.js index 3777f24..45dfd0b 100644 --- a/lib/drush-node.js +++ b/lib/drush-node.js @@ -54,8 +54,9 @@ Drush.version = function () { /** * Execute a drush command. * - * @param {string} command - * The drush command to execute. + * @param {string|array} args + * The drush command to execute either as a string or an array of arguments, + * e.g. 'cc drush' or ['cc', 'drush']. * @param {object} options * A hash of options to add to the command, can contain: * - log: flag to log the output of the drush command. @@ -67,9 +68,12 @@ Drush.version = function () { * * @return Promise */ -Drush.exec = function (command, options) { +Drush.exec = function (args, options) { options = options || {}; - var args = command.split(' ') || ''; + if (typeof args === 'string') { + args = args.split(' '); + } + args = args || []; var log = typeof options.log !== 'undefined' ? options.log : this.execOptions.log; var def = new Deferred(); var output = ''; @@ -98,8 +102,8 @@ Drush.exec = function (command, options) { // Handle echo and cat options. if (options.echo || options.cat) { - var pipeCommand = options.echo ? 'echo' : 'cat'; - var cmd = spawn(pipeCommand, options[pipeCommand].split(' ')); + var command = options.echo ? 'echo' : 'cat'; + var cmd = spawn(command, options[command].split(' ')); cmd.stdout.on('data', function (data) { drush.stdin.write(data); @@ -111,7 +115,7 @@ Drush.exec = function (command, options) { }); cmd.on('close', function (code) { if (code !== 0) { - return def.reject(pipeCommand + ' process exited with code ' + code); + return def.reject(command + ' process exited with code ' + code); } drush.stdin.end(); }); From 6e235bf22933c4be7362dfbca1f4ba43e13e28e1 Mon Sep 17 00:00:00 2001 From: Peter Sieg Date: Tue, 19 May 2015 12:03:49 -0400 Subject: [PATCH 07/10] Fix typo in documentation. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c74d47f..43a5063 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ drush.init().then( ); // Executes `drush cc all` and logs the output as the command is running. - drush.exec('cc all'], {log: true}); + drush.exec('cc all', {log: true}); } ); ``` From 0cdee59ef29df9434d3bc6335623a3bad3bd34eb Mon Sep 17 00:00:00 2001 From: Peter Sieg Date: Tue, 19 May 2015 12:16:02 -0400 Subject: [PATCH 08/10] Use node-argv to parse arguments when they are passed to exec as a string. --- lib/drush-node.js | 3 ++- package.json | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/drush-node.js b/lib/drush-node.js index 45dfd0b..28b250b 100644 --- a/lib/drush-node.js +++ b/lib/drush-node.js @@ -1,6 +1,7 @@ var _ = require('lodash'); var exec = require('child_process').exec; var spawn = require('child_process').spawn; +var argv = require('node-argv'); var Deferred = require('promised-io/promise').Deferred; var Drush = { @@ -71,7 +72,7 @@ Drush.version = function () { Drush.exec = function (args, options) { options = options || {}; if (typeof args === 'string') { - args = args.split(' '); + args = argv(args, {}).input; } args = args || []; var log = typeof options.log !== 'undefined' ? options.log : this.execOptions.log; diff --git a/package.json b/package.json index 942776b..2c5c09d 100644 --- a/package.json +++ b/package.json @@ -25,8 +25,9 @@ } ], "dependencies": { - "promised-io": "^0.3.4", - "lodash": "^2.4.1" + "lodash": "^2.4.1", + "node-argv": "0.0.7", + "promised-io": "^0.3.4" }, "devDependencies": { "grunt": "^0.4.4", From abd5ce7d4fa75d7d115edab20b32f9af945fa229 Mon Sep 17 00:00:00 2001 From: Peter Sieg Date: Tue, 19 May 2015 12:51:44 -0400 Subject: [PATCH 09/10] Actually pass the execOptions to spawn(). --- lib/drush-node.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/drush-node.js b/lib/drush-node.js index 28b250b..a190239 100644 --- a/lib/drush-node.js +++ b/lib/drush-node.js @@ -99,7 +99,7 @@ Drush.exec = function (args, options) { args.push('-y'); // Initialize drush child process. - var drush = spawn(Drush.command, args); + var drush = spawn(Drush.command, args, this.execOptions); // Handle echo and cat options. if (options.echo || options.cat) { From 0306d0e9ca3f54582e7c28fe45cfe2a88a351181 Mon Sep 17 00:00:00 2001 From: Jon Peck Date: Wed, 30 Sep 2015 18:51:11 -0700 Subject: [PATCH 10/10] Fixing double-spaced output, documentation --- lib/drush-node.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/drush-node.js b/lib/drush-node.js index a190239..b6c955e 100644 --- a/lib/drush-node.js +++ b/lib/drush-node.js @@ -11,10 +11,10 @@ var Drush = { }; /** - * Initializes the drush object by determining the path to the drush - * executable. + * Initializes the drush object by determining path to the drush executable. * - * @return Promise. + * @param {Object} execOptions - child_process.spawn Asynchronous Process Creation options. + * @returns {Promise}. */ Drush.init = function (execOptions) { this.execOptions = _.merge(this.execOptions, execOptions || {}); @@ -34,7 +34,7 @@ Drush.init = function (execOptions) { /** * Fetches the Drush version on the host machine. * - * @return Promise. + * @returns {Promise} upon completion. */ Drush.version = function () { var def = new Deferred(); @@ -67,7 +67,7 @@ Drush.version = function () { * - echo: text to echo to the drush command. * - cat: a file to cat to the drush command. * - * @return Promise + * @returns {Promise} upon completion. */ Drush.exec = function (args, options) { options = options || {}; @@ -127,13 +127,13 @@ Drush.exec = function (args, options) { drush.stdout.on('data', function (data) { output += data; if (log) { - console.log('' + data); + console.log(data.toString('utf8').trim()); } }); drush.stderr.on('data', function (data) { output += data; if (log) { - console.log('' + data); + console.log(data.toString('utf8').trim()); } }); drush.on('close', function (code) { @@ -147,4 +147,3 @@ Drush.exec = function (args, options) { }; module.exports = Drush; -