Catch leaked exceptions in superspawn and convert them to rejections
At least until Node.js 8, child_process.spawn will throw exceptions
instead of emitting error events in certain cases (like EACCES), Thus we
have to wrap the execution in try/catch to convert them into rejections.
diff --git a/spec/superspawn.spec.js b/spec/superspawn.spec.js
index 95056dc..9c05129 100644
--- a/spec/superspawn.spec.js
+++ b/spec/superspawn.spec.js
@@ -89,6 +89,25 @@
});
});
+ it('Test 005 : should not throw but reject', () => {
+ if (process.platform === 'win32') {
+ pending('Test should not run on Windows');
+ }
+
+ // Our non-executable (as in no execute permission) script
+ const TEST_SCRIPT = path.join(__dirname, 'fixtures/echo-args.cmd');
+
+ let promise;
+ expect(() => { promise = superspawn.spawn(TEST_SCRIPT, []); }).not.toThrow();
+
+ return Promise.resolve(promise).then(_ => {
+ fail('Expected promise to be rejected');
+ }, err => {
+ expect(err).toEqual(jasmine.any(Error));
+ expect(err.code).toBe('EACCES');
+ });
+ });
+
describe('operation on windows', () => {
const TEST_SCRIPT = path.join(__dirname, 'fixtures/echo-args.cmd');
const TEST_ARGS = [ 'install', 'foo@^1.2.3', 'c o r d o v a' ];
diff --git a/src/superspawn.js b/src/superspawn.js
index 22daf88..2e58b2e 100644
--- a/src/superspawn.js
+++ b/src/superspawn.js
@@ -87,7 +87,15 @@
events.emit(opts.printCommand ? 'log' : 'verbose', 'Running command: ' + cmd + ' ' + args.join(' '));
- var child = crossSpawn.spawn(cmd, args, spawnOpts);
+ // At least until Node.js 8, child_process.spawn will throw exceptions
+ // instead of emitting error events in certain cases (like EACCES), Thus we
+ // have to wrap the execution in try/catch to convert them into rejections.
+ try {
+ var child = crossSpawn.spawn(cmd, args, spawnOpts);
+ } catch (e) {
+ whenDone(e);
+ return d.promise;
+ }
var capturedOut = '';
var capturedErr = '';
@@ -110,8 +118,10 @@
child.on('close', whenDone);
child.on('error', whenDone);
function whenDone (arg) {
- child.removeListener('close', whenDone);
- child.removeListener('error', whenDone);
+ if (child) {
+ child.removeListener('close', whenDone);
+ child.removeListener('error', whenDone);
+ }
var code = typeof arg === 'number' ? arg : arg && arg.code;
events.emit('verbose', 'Command finished with error code ' + code + ': ' + cmd + ' ' + args);