mirror of
https://github.com/shelljs/shelljs.git
synced 2026-01-18 16:03:37 +00:00
Merge pull request #268 from nfischer/MultipleErrorMessages
Commands that have multiple errors now produce cleaner log output
This commit is contained in:
commit
99f71be89d
15
README.md
15
README.md
@ -481,23 +481,24 @@ Available options (all `false` by default):
|
||||
Examples:
|
||||
|
||||
```javascript
|
||||
var version = exec('node --version', {silent:true}).output;
|
||||
var version = exec('node --version', {silent:true}).stdout;
|
||||
|
||||
var child = exec('some_long_running_process', {async:true});
|
||||
child.stdout.on('data', function(data) {
|
||||
/* ... do something with data ... */
|
||||
});
|
||||
|
||||
exec('some_long_running_process', function(code, output) {
|
||||
exec('some_long_running_process', function(code, stdout, stderr) {
|
||||
console.log('Exit code:', code);
|
||||
console.log('Program output:', output);
|
||||
console.log('Program output:', stdout);
|
||||
console.log('Program stderr:', stderr);
|
||||
});
|
||||
```
|
||||
|
||||
Executes the given `command` _synchronously_, unless otherwise specified.
|
||||
When in synchronous mode returns the object `{ code:..., output:... }`, containing the program's
|
||||
`output` (stdout + stderr) and its exit `code`. Otherwise returns the child process object, and
|
||||
the `callback` gets the arguments `(code, output)`.
|
||||
Executes the given `command` _synchronously_, unless otherwise specified. When in synchronous
|
||||
mode returns the object `{ code:..., stdout:... , stderr:... }`, containing the program's
|
||||
`stdout`, `stderr`, and its exit `code`. Otherwise returns the child process object,
|
||||
and the `callback` gets the arguments `(code, stdout, stderr)`.
|
||||
|
||||
**Note:** For long-lived processes, it's best to run `exec()` asynchronously as
|
||||
the current synchronous implementation uses a lot of CPU. This should be getting
|
||||
|
||||
@ -21,7 +21,7 @@ exports.platform = platform;
|
||||
|
||||
function log() {
|
||||
if (!config.silent)
|
||||
console.log.apply(this, arguments);
|
||||
console.error.apply(this, arguments);
|
||||
}
|
||||
exports.log = log;
|
||||
|
||||
@ -29,10 +29,14 @@ exports.log = log;
|
||||
function error(msg, _continue) {
|
||||
if (state.error === null)
|
||||
state.error = '';
|
||||
state.error += state.currentCmd + ': ' + msg + '\n';
|
||||
var log_entry = state.currentCmd + ': ' + msg;
|
||||
if (state.error === '')
|
||||
state.error = log_entry;
|
||||
else
|
||||
state.error += '\n' + log_entry;
|
||||
|
||||
if (msg.length > 0)
|
||||
log(state.error);
|
||||
log(log_entry);
|
||||
|
||||
if (config.fatal)
|
||||
process.exit(1);
|
||||
|
||||
78
src/exec.js
78
src/exec.js
@ -13,6 +13,7 @@ var child = require('child_process');
|
||||
function execSync(cmd, opts) {
|
||||
var tempDir = _tempDir();
|
||||
var stdoutFile = path.resolve(tempDir+'/'+common.randomFileName()),
|
||||
stderrFile = path.resolve(tempDir+'/'+common.randomFileName()),
|
||||
codeFile = path.resolve(tempDir+'/'+common.randomFileName()),
|
||||
scriptFile = path.resolve(tempDir+'/'+common.randomFileName()),
|
||||
sleepFile = path.resolve(tempDir+'/'+common.randomFileName());
|
||||
@ -21,19 +22,30 @@ function execSync(cmd, opts) {
|
||||
silent: common.config.silent
|
||||
}, opts);
|
||||
|
||||
var previousStdoutContent = '';
|
||||
// Echoes stdout changes from running process, if not silent
|
||||
function updateStdout() {
|
||||
if (options.silent || !fs.existsSync(stdoutFile))
|
||||
var previousStdoutContent = '',
|
||||
previousStderrContent = '';
|
||||
// Echoes stdout and stderr changes from running process, if not silent
|
||||
function updateStream(streamFile) {
|
||||
if (options.silent || !fs.existsSync(streamFile))
|
||||
return;
|
||||
|
||||
var stdoutContent = fs.readFileSync(stdoutFile, 'utf8');
|
||||
var previousStreamContent,
|
||||
proc_stream;
|
||||
if (streamFile === stdoutFile) {
|
||||
previousStreamContent = previousStdoutContent;
|
||||
proc_stream = process.stdout;
|
||||
} else { // assume stderr
|
||||
previousStreamContent = previousStderrContent;
|
||||
proc_stream = process.stderr;
|
||||
}
|
||||
|
||||
var streamContent = fs.readFileSync(streamFile, 'utf8');
|
||||
// No changes since last time?
|
||||
if (stdoutContent.length <= previousStdoutContent.length)
|
||||
if (streamContent.length <= previousStreamContent.length)
|
||||
return;
|
||||
|
||||
process.stdout.write(stdoutContent.substr(previousStdoutContent.length));
|
||||
previousStdoutContent = stdoutContent;
|
||||
proc_stream.write(streamContent.substr(previousStreamContent.length));
|
||||
previousStreamContent = streamContent;
|
||||
}
|
||||
|
||||
function escape(str) {
|
||||
@ -42,6 +54,7 @@ function execSync(cmd, opts) {
|
||||
|
||||
if (fs.existsSync(scriptFile)) common.unlinkSync(scriptFile);
|
||||
if (fs.existsSync(stdoutFile)) common.unlinkSync(stdoutFile);
|
||||
if (fs.existsSync(stderrFile)) common.unlinkSync(stderrFile);
|
||||
if (fs.existsSync(codeFile)) common.unlinkSync(codeFile);
|
||||
|
||||
var execCommand = '"'+process.execPath+'" '+scriptFile;
|
||||
@ -59,14 +72,16 @@ function execSync(cmd, opts) {
|
||||
" fs.writeFileSync('"+escape(codeFile)+"', err ? err.code.toString() : '0');",
|
||||
"});",
|
||||
"var stdoutStream = fs.createWriteStream('"+escape(stdoutFile)+"');",
|
||||
"var stderrStream = fs.createWriteStream('"+escape(stderrFile)+"');",
|
||||
"childProcess.stdout.pipe(stdoutStream, {end: false});",
|
||||
"childProcess.stderr.pipe(stdoutStream, {end: false});",
|
||||
"childProcess.stderr.pipe(stderrStream, {end: false});",
|
||||
"childProcess.stdout.pipe(process.stdout);",
|
||||
"childProcess.stderr.pipe(process.stderr);",
|
||||
"var stdoutEnded = false, stderrEnded = false;",
|
||||
"function tryClosing(){ if(stdoutEnded && stderrEnded){ stdoutStream.end(); } }",
|
||||
"childProcess.stdout.on('end', function(){ stdoutEnded = true; tryClosing(); });",
|
||||
"childProcess.stderr.on('end', function(){ stderrEnded = true; tryClosing(); });"
|
||||
"function tryClosingStdout(){ if(stdoutEnded){ stdoutStream.end(); } }",
|
||||
"function tryClosingStderr(){ if(stderrEnded){ stderrStream.end(); } }",
|
||||
"childProcess.stdout.on('end', function(){ stdoutEnded = true; tryClosingStdout(); });",
|
||||
"childProcess.stderr.on('end', function(){ stderrEnded = true; tryClosingStderr(); });"
|
||||
].join('\n');
|
||||
|
||||
fs.writeFileSync(scriptFile, script);
|
||||
@ -80,7 +95,7 @@ function execSync(cmd, opts) {
|
||||
// Welcome to the future
|
||||
child.execSync(execCommand, execOptions);
|
||||
} else {
|
||||
cmd += ' > '+stdoutFile+' 2>&1'; // works on both win/unix
|
||||
cmd += ' > '+stdoutFile+' 2> '+stderrFile; // works on both win/unix
|
||||
|
||||
var script = [
|
||||
"var child = require('child_process')",
|
||||
@ -98,8 +113,9 @@ function execSync(cmd, opts) {
|
||||
// sleepFile is used as a dummy I/O op to mitigate unnecessary CPU usage
|
||||
// (tried many I/O sync ops, writeFileSync() seems to be only one that is effective in reducing
|
||||
// CPU usage, though apparently not so much on Windows)
|
||||
while (!fs.existsSync(codeFile)) { updateStdout(); fs.writeFileSync(sleepFile, 'a'); }
|
||||
while (!fs.existsSync(stdoutFile)) { updateStdout(); fs.writeFileSync(sleepFile, 'a'); }
|
||||
while (!fs.existsSync(codeFile)) { updateStream(stdoutFile); fs.writeFileSync(sleepFile, 'a'); }
|
||||
while (!fs.existsSync(stdoutFile)) { updateStream(stdoutFile); fs.writeFileSync(sleepFile, 'a'); }
|
||||
while (!fs.existsSync(stderrFile)) { updateStream(stderrFile); fs.writeFileSync(sleepFile, 'a'); }
|
||||
}
|
||||
|
||||
// At this point codeFile exists, but it's not necessarily flushed yet.
|
||||
@ -110,10 +126,12 @@ function execSync(cmd, opts) {
|
||||
}
|
||||
|
||||
var stdout = fs.readFileSync(stdoutFile, 'utf8');
|
||||
var stderr = fs.readFileSync(stderrFile, 'utf8');
|
||||
|
||||
// No biggie if we can't erase the files now -- they're in a temp dir anyway
|
||||
try { common.unlinkSync(scriptFile); } catch(e) {}
|
||||
try { common.unlinkSync(stdoutFile); } catch(e) {}
|
||||
try { common.unlinkSync(stderrFile); } catch(e) {}
|
||||
try { common.unlinkSync(codeFile); } catch(e) {}
|
||||
try { common.unlinkSync(sleepFile); } catch(e) {}
|
||||
|
||||
@ -124,14 +142,17 @@ function execSync(cmd, opts) {
|
||||
// True if successful, false if not
|
||||
var obj = {
|
||||
code: code,
|
||||
output: stdout
|
||||
output: stdout, // deprecated
|
||||
stdout: stdout,
|
||||
stderr: stderr
|
||||
};
|
||||
return obj;
|
||||
} // execSync()
|
||||
|
||||
// Wrapper around exec() to enable echoing output to console in real time
|
||||
function execAsync(cmd, opts, callback) {
|
||||
var output = '';
|
||||
var stdout = '';
|
||||
var stderr = '';
|
||||
|
||||
var options = common.extend({
|
||||
silent: common.config.silent
|
||||
@ -139,19 +160,19 @@ function execAsync(cmd, opts, callback) {
|
||||
|
||||
var c = child.exec(cmd, {env: process.env, maxBuffer: 20*1024*1024}, function(err) {
|
||||
if (callback)
|
||||
callback(err ? err.code : 0, output);
|
||||
callback(err ? err.code : 0, stdout, stderr);
|
||||
});
|
||||
|
||||
c.stdout.on('data', function(data) {
|
||||
output += data;
|
||||
stdout += data;
|
||||
if (!options.silent)
|
||||
process.stdout.write(data);
|
||||
});
|
||||
|
||||
c.stderr.on('data', function(data) {
|
||||
output += data;
|
||||
stderr += data;
|
||||
if (!options.silent)
|
||||
process.stdout.write(data);
|
||||
process.stderr.write(data);
|
||||
});
|
||||
|
||||
return c;
|
||||
@ -167,23 +188,24 @@ function execAsync(cmd, opts, callback) {
|
||||
//@ Examples:
|
||||
//@
|
||||
//@ ```javascript
|
||||
//@ var version = exec('node --version', {silent:true}).output;
|
||||
//@ var version = exec('node --version', {silent:true}).stdout;
|
||||
//@
|
||||
//@ var child = exec('some_long_running_process', {async:true});
|
||||
//@ child.stdout.on('data', function(data) {
|
||||
//@ /* ... do something with data ... */
|
||||
//@ });
|
||||
//@
|
||||
//@ exec('some_long_running_process', function(code, output) {
|
||||
//@ exec('some_long_running_process', function(code, stdout, stderr) {
|
||||
//@ console.log('Exit code:', code);
|
||||
//@ console.log('Program output:', output);
|
||||
//@ console.log('Program output:', stdout);
|
||||
//@ console.log('Program stderr:', stderr);
|
||||
//@ });
|
||||
//@ ```
|
||||
//@
|
||||
//@ Executes the given `command` _synchronously_, unless otherwise specified.
|
||||
//@ When in synchronous mode returns the object `{ code:..., output:... }`, containing the program's
|
||||
//@ `output` (stdout + stderr) and its exit `code`. Otherwise returns the child process object, and
|
||||
//@ the `callback` gets the arguments `(code, output)`.
|
||||
//@ Executes the given `command` _synchronously_, unless otherwise specified. When in synchronous
|
||||
//@ mode returns the object `{ code:..., stdout:... , stderr:... }`, containing the program's
|
||||
//@ `stdout`, `stderr`, and its exit `code`. Otherwise returns the child process object,
|
||||
//@ and the `callback` gets the arguments `(code, stdout, stderr)`.
|
||||
//@
|
||||
//@ **Note:** For long-lived processes, it's best to run `exec()` asynchronously as
|
||||
//@ the current synchronous implementation uses a lot of CPU. This should be getting
|
||||
|
||||
@ -1,14 +1,11 @@
|
||||
var shell = require('..');
|
||||
|
||||
var assert = require('assert'),
|
||||
fs = require('fs');
|
||||
fs = require('fs'),
|
||||
numLines = require('./utils/utils').numLines;
|
||||
|
||||
shell.config.silent = true;
|
||||
|
||||
function numLines(str) {
|
||||
return typeof str === 'string' ? str.match(/\n/g).length : 0;
|
||||
}
|
||||
|
||||
shell.rm('-rf', 'tmp');
|
||||
shell.mkdir('tmp');
|
||||
|
||||
|
||||
27
test/exec.js
27
test/exec.js
@ -44,19 +44,21 @@ process.exit = old_exit;
|
||||
var result = shell.exec('node -e \"console.log(1234);\"');
|
||||
assert.equal(shell.error(), null);
|
||||
assert.equal(result.code, 0);
|
||||
assert.ok(result.output === '1234\n' || result.output === '1234\nundefined\n'); // 'undefined' for v0.4
|
||||
assert.ok(result.stdout === '1234\n' || result.stdout === '1234\nundefined\n'); // 'undefined' for v0.4
|
||||
|
||||
// check if stderr goes to output
|
||||
var result = shell.exec('node -e \"console.error(1234);\"');
|
||||
assert.equal(shell.error(), null);
|
||||
assert.equal(result.code, 0);
|
||||
assert.ok(result.output === '1234\n' || result.output === '1234\nundefined\n'); // 'undefined' for v0.4
|
||||
assert.ok(result.stdout === '' || result.stdout === 'undefined\n'); // 'undefined' for v0.4
|
||||
assert.ok(result.stderr === '1234\n' || result.stderr === '1234\nundefined\n'); // 'undefined' for v0.4
|
||||
|
||||
// check if stdout + stderr go to output
|
||||
var result = shell.exec('node -e \"console.error(1234); console.log(666);\"');
|
||||
assert.equal(shell.error(), null);
|
||||
assert.equal(result.code, 0);
|
||||
assert.ok(result.output === '1234\n666\n' || result.output === '1234\n666\nundefined\n'); // 'undefined' for v0.4
|
||||
assert.ok(result.stdout === '666\n' || result.stdout === '666\nundefined\n'); // 'undefined' for v0.4
|
||||
assert.ok(result.stderr === '1234\n' || result.stderr === '1234\nundefined\n'); // 'undefined' for v0.4
|
||||
|
||||
// check exit code
|
||||
var result = shell.exec('node -e \"process.exit(12);\"');
|
||||
@ -68,14 +70,14 @@ shell.cd('resources/external');
|
||||
var result = shell.exec('node node_script.js');
|
||||
assert.equal(shell.error(), null);
|
||||
assert.equal(result.code, 0);
|
||||
assert.equal(result.output, 'node_script_1234\n');
|
||||
assert.equal(result.stdout, 'node_script_1234\n');
|
||||
shell.cd('../..');
|
||||
|
||||
// check quotes escaping
|
||||
var result = shell.exec( util.format('node -e "console.log(%s);"', "\\\"\\'+\\'_\\'+\\'\\\"") );
|
||||
assert.equal(shell.error(), null);
|
||||
assert.equal(result.code, 0);
|
||||
assert.equal(result.output, "'+'_'+'\n");
|
||||
assert.equal(result.stdout, "'+'_'+'\n");
|
||||
|
||||
//
|
||||
// async
|
||||
@ -89,23 +91,26 @@ assert.ok('stdout' in c, 'async exec returns child process object');
|
||||
//
|
||||
// callback as 2nd argument
|
||||
//
|
||||
shell.exec('node -e \"console.log(5678);\"', function(code, output) {
|
||||
shell.exec('node -e \"console.log(5678);\"', function(code, stdout, stderr) {
|
||||
assert.equal(code, 0);
|
||||
assert.ok(output === '5678\n' || output === '5678\nundefined\n'); // 'undefined' for v0.4
|
||||
assert.ok(stdout === '5678\n' || stdout === '5678\nundefined\n'); // 'undefined' for v0.4
|
||||
assert.ok(stderr === '' || stderr === 'undefined\n'); // 'undefined' for v0.4
|
||||
|
||||
//
|
||||
// callback as 3rd argument
|
||||
//
|
||||
shell.exec('node -e \"console.log(5566);\"', {async:true}, function(code, output) {
|
||||
shell.exec('node -e \"console.log(5566);\"', {async:true}, function(code, stdout, stderr) {
|
||||
assert.equal(code, 0);
|
||||
assert.ok(output === '5566\n' || output === '5566\nundefined\n'); // 'undefined' for v0.4
|
||||
assert.ok(stdout === '5566\n' || stdout === '5566\nundefined\n'); // 'undefined' for v0.4
|
||||
assert.ok(stderr === '' || stderr === 'undefined\n'); // 'undefined' for v0.4
|
||||
|
||||
//
|
||||
// callback as 3rd argument (slient:true)
|
||||
//
|
||||
shell.exec('node -e \"console.log(5678);\"', {silent:true}, function(code, output) {
|
||||
shell.exec('node -e \"console.log(5678);\"', {silent:true}, function(code, stdout, stderr) {
|
||||
assert.equal(code, 0);
|
||||
assert.ok(output === '5678\n' || output === '5678\nundefined\n'); // 'undefined' for v0.4
|
||||
assert.ok(stdout === '5678\n' || stdout === '5678\nundefined\n'); // 'undefined' for v0.4
|
||||
assert.ok(stderr === '' || stderr === 'undefined\n'); // 'undefined' for v0.4
|
||||
|
||||
shell.exit(123);
|
||||
|
||||
|
||||
@ -1,14 +1,11 @@
|
||||
var shell = require('..');
|
||||
|
||||
var assert = require('assert'),
|
||||
fs = require('fs');
|
||||
fs = require('fs'),
|
||||
numLines = require('./utils/utils').numLines;
|
||||
|
||||
shell.config.silent = true;
|
||||
|
||||
function numLines(str) {
|
||||
return typeof str === 'string' ? str.match(/\n/g).length : 0;
|
||||
}
|
||||
|
||||
shell.rm('-rf', 'tmp');
|
||||
shell.mkdir('tmp');
|
||||
|
||||
|
||||
@ -1,14 +1,11 @@
|
||||
var shell = require('..');
|
||||
|
||||
var assert = require('assert'),
|
||||
fs = require('fs');
|
||||
fs = require('fs'),
|
||||
numLines = require('./utils/utils').numLines;
|
||||
|
||||
shell.config.silent = true;
|
||||
|
||||
function numLines(str) {
|
||||
return typeof str === 'string' ? str.match(/\n/g).length : 0;
|
||||
}
|
||||
|
||||
shell.rm('-rf', 'tmp');
|
||||
shell.mkdir('tmp');
|
||||
|
||||
|
||||
@ -183,7 +183,7 @@ assert.deepEqual(trail, [
|
||||
|
||||
// Push invalid directory
|
||||
shell.pushd('does/not/exist');
|
||||
assert.equal(shell.error(), 'pushd: no such file or directory: ' + path.resolve('.', 'does/not/exist') + '\n');
|
||||
assert.equal(shell.error(), 'pushd: no such file or directory: ' + path.resolve('.', 'does/not/exist'));
|
||||
assert.equal(process.cwd(), trail[0]);
|
||||
|
||||
// Push without arguments should swap top two directories when stack length is 2
|
||||
@ -219,6 +219,6 @@ assert.equal(process.cwd(), trail[0]);
|
||||
|
||||
// Push without arguments invalid when stack is empty
|
||||
reset(); shell.pushd();
|
||||
assert.equal(shell.error(), 'pushd: no other directory\n');
|
||||
assert.equal(shell.error(), 'pushd: no other directory');
|
||||
|
||||
shell.exit(123);
|
||||
shell.exit(123);
|
||||
|
||||
5
test/utils/utils.js
Normal file
5
test/utils/utils.js
Normal file
@ -0,0 +1,5 @@
|
||||
function _numLines(str) {
|
||||
return typeof str === 'string' ? (str.match(/\n/g)||[]).length+1 : 0;
|
||||
}
|
||||
|
||||
exports.numLines = _numLines;
|
||||
Loading…
x
Reference in New Issue
Block a user