From a3b698d0169c1040e6ea7c9e27e65996272aa07a Mon Sep 17 00:00:00 2001 From: Ryan Hendrickson Date: Mon, 2 Oct 2017 18:29:18 -0400 Subject: [PATCH] Fix: improve escaping for script arguments **Summary** Extra command-line arguments to scripts were not being escaped correctly. This patch uses puka to add robust shell quoting for both Windows and Linux/macOS. **Test plan** On *nix, create a `package.json` containing `"scripts":{"echo":"echo"}`. Run `yarn run -s echo -- '$X \"blah\"'`. Expect to observe ` \blah\` prior to this patch, and `$X \"blah\"` after it. Testing on Windows should be similar, but may require fancier escaping to get the arguments into yarn in the first place. --- __tests__/commands/run.js | 14 ++++++---- __tests__/integration.js | 58 ++++++++++++++++----------------------- package.json | 1 + src/cli/commands/run.js | 11 ++------ src/util/misc.js | 18 ------------ yarn.lock | 4 +++ 6 files changed, 39 insertions(+), 67 deletions(-) diff --git a/__tests__/commands/run.js b/__tests__/commands/run.js index 386c7e3bd7..7477289c04 100644 --- a/__tests__/commands/run.js +++ b/__tests__/commands/run.js @@ -64,7 +64,7 @@ test('properly handles extra arguments and pre/post scripts', (): Promise const pkg = await fs.readJson(path.join(config.cwd, 'package.json')); const poststart = ['poststart', config, pkg.scripts.poststart, config.cwd]; const prestart = ['prestart', config, pkg.scripts.prestart, config.cwd]; - const start = ['start', config, pkg.scripts.start + ' "--hello"', config.cwd]; + const start = ['start', config, pkg.scripts.start + ' --hello', config.cwd]; expect(execCommand.mock.calls[0]).toEqual(prestart); expect(execCommand.mock.calls[1]).toEqual(start); @@ -75,7 +75,7 @@ test('properly handles extra arguments and pre/post scripts', (): Promise test('properly handle bin scripts', (): Promise => { return runRun(['cat-names'], {}, 'bin', config => { const script = path.join(config.cwd, 'node_modules', '.bin', 'cat-names'); - const args = ['cat-names', config, `"${script}"`, config.cwd]; + const args = ['cat-names', config, script, config.cwd]; expect(execCommand).toBeCalledWith(...args); }); @@ -114,19 +114,21 @@ test('properly handle env command', (): Promise => { }); }); -test('retains string delimiters if args have spaces', (): Promise => { +test('adds string delimiters if args have spaces', (): Promise => { return runRun(['cat-names', '--filter', 'cat names'], {}, 'bin', config => { const script = path.join(config.cwd, 'node_modules', '.bin', 'cat-names'); - const args = ['cat-names', config, `"${script}" "--filter" "cat names"`, config.cwd]; + const q = process.platform === 'win32' ? '"' : "'"; + const args = ['cat-names', config, `${script} --filter ${q}cat names${q}`, config.cwd]; expect(execCommand).toBeCalledWith(...args); }); }); -test('retains quotes if args have spaces and quotes', (): Promise => { +test('adds quotes if args have spaces and quotes', (): Promise => { return runRun(['cat-names', '--filter', '"cat names"'], {}, 'bin', config => { const script = path.join(config.cwd, 'node_modules', '.bin', 'cat-names'); - const args = ['cat-names', config, `"${script}" "--filter" "\\"cat names\\""`, config.cwd]; + const quotedCatNames = process.platform === 'win32' ? '^"\\^"cat^ names\\^"^"' : `'"cat names"'`; + const args = ['cat-names', config, `${script} --filter ${quotedCatNames}`, config.cwd]; expect(execCommand).toBeCalledWith(...args); }); diff --git a/__tests__/integration.js b/__tests__/integration.js index 5c44f2310a..4533f8c55f 100644 --- a/__tests__/integration.js +++ b/__tests__/integration.js @@ -2,9 +2,9 @@ /* eslint max-len: 0 */ import execa from 'execa'; +import {sh} from 'puka'; import makeTemp from './_temp.js'; import * as fs from '../src/util/fs.js'; -import * as misc from '../src/util/misc.js'; import * as constants from '../src/constants.js'; jasmine.DEFAULT_TIMEOUT_INTERVAL = 120000; @@ -76,7 +76,7 @@ async function runYarn(args: Array = [], options: Object = {}): Promise< options['extendEnv'] = false; } options['env']['FORCE_COLOR'] = 0; - const {stdout, stderr} = await execa(path.resolve(__dirname, '../bin/yarn'), args, options); + const {stdout, stderr} = await execa.shell(sh`${path.resolve(__dirname, '../bin/yarn')} ${args}`, options); return [stdout, stderr]; } @@ -211,9 +211,8 @@ test('yarnrc binary path (executable)', async () => { expect(stdoutOutput.toString().trim()).toEqual('override called'); }); -// Windows could run these tests, but we currently suffer from an escaping issue that breaks them (#4135) -if (process.platform !== 'win32') { - test('yarn run