Skip to content

Commit

Permalink
Fix build on Windows for user paths with spaces
Browse files Browse the repository at this point in the history
Fix incorrect command and argument handling on Windows by spawning the
command in a shell and by wrapping command and arguments containing
spaces in quotes.

See nodejs/node#5060

Fix #25

Change-Id: Ieb32892946e9a779b67b5842f4f58d18be847b0f
  • Loading branch information
cpetrov committed Aug 17, 2017
1 parent e17291e commit 77ab529
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 4 deletions.
30 changes: 26 additions & 4 deletions src/helpers/proc.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,37 @@
const os = require('os');
const {spawnSync} = require('child_process');
const proc = require('child_process');
const log = require('./log');

function execSync(cmd, args, opts = {}) {
let cmdName = os.platform() === 'win32' ? cmd + '.cmd' : cmd;
log.command([cmdName, ...args].join(' '), opts.cwd);
const ps = spawnSync(cmdName, args, Object.assign({stdio: 'inherit'}, opts));
let normalizedCmd = normalizeCommand(cmd);
let normalizedArgs = normalizeArguments(args);
log.command([normalizedCmd, ...normalizedArgs].join(' '), opts.cwd);
const ps = proc.spawnSync(normalizedCmd, normalizedArgs, Object.assign({
stdio: 'inherit',
shell: isWindows()
}, opts));
if (ps.status !== 0) {
throw new Error(`The command ${cmd} exited with ${ps.status}`);
}
return ps;
}

function normalizeArguments(args) {
if (isWindows()) {
return args.map(arg => arg.indexOf(' ') >= 0 ? `"${arg}"` : arg);
}
return args;
}

function normalizeCommand(cmd) {
if (isWindows()) {
return cmd.indexOf(' ') >= 0 ? `"${cmd}"` : cmd;
}
return cmd;
}

function isWindows() {
return os.platform() === 'win32';
}

module.exports = {execSync};
70 changes: 70 additions & 0 deletions test/proc.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
const os = require('os');
const childProcess = require('child_process');
const proc = require('../src/helpers/proc');
const log = require('../src/helpers/log');
const {expect, stub, restore, match} = require('./test');

describe('proc', function() {

describe('execSync', function() {

let status, platform;

beforeEach(function() {
status = 0;
platform = 'linux';
stub(log, 'command');
stub(os, 'platform').callsFake(() => platform);
stub(childProcess, 'spawnSync').callsFake(() => ({status}));
});

afterEach(restore);

it('spawns command with options', function() {
proc.execSync('foo', ['ba r', 'bak'], {option: 'value'});

expect(childProcess.spawnSync).to.have.been.calledWithMatch('foo', ['ba r', 'bak'], {
stdio: 'inherit',
shell: false,
option: 'value'
});
});

it('runs command inside of a shell on Windows', function() {
platform = 'win32';

proc.execSync('foo', ['bar'], {option: 'value'});

expect(childProcess.spawnSync).to.have.been.calledWithMatch(match.any, match.any, {
shell: true
});
});

it('throws an error when process exits with non 0 status', function() {
status = 123;

expect(() => {
proc.execSync('foo', ['bar'], {option: 'value'});
}).to.throw('The command foo exited with 123');
});

it('normalizes command on Windows', function() {
platform = 'win32';

proc.execSync('fo o', ['bar'], {option: 'value'});

expect(childProcess.spawnSync).to.have.been.calledWithMatch('"fo o"');
});

it('normalizes arguments on Windows', function() {
platform = 'win32';

proc.execSync('foo', ['bar', 'ba k'], {option: 'value'});

expect(childProcess.spawnSync).to.have.been.calledWithMatch(match.any, ['bar', '"ba k"']);
});

});

});

0 comments on commit 77ab529

Please sign in to comment.