diff --git a/declarations/escape-it.d.ts b/declarations/escape-it.d.ts new file mode 100644 index 00000000..8d660504 --- /dev/null +++ b/declarations/escape-it.d.ts @@ -0,0 +1,4 @@ +declare module 'escape-it' { + function escape(platform?: string): (...args: string[]) => string; + export default escape; +} diff --git a/package-lock.json b/package-lock.json index 696ed5da..1317bc15 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,9 +11,9 @@ "dependencies": { "chalk": "^4.1.0", "date-fns": "^2.29.1", + "escape-it": "^0.3.0", "lodash": "^4.17.21", "rxjs": "^7.0.0", - "shell-quote": "^1.7.3", "spawn-command": "^0.0.2-1", "supports-color": "^8.1.0", "tree-kill": "^1.2.2", @@ -3161,6 +3161,11 @@ "node": ">=6" } }, + "node_modules/escape-it": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/escape-it/-/escape-it-0.3.0.tgz", + "integrity": "sha512-LgPtY7WCP1wcIgL591yG+01YzstxxZaHS1ZjTnQLFGWG+ZF/cjBIGh1C7uf2xlRlq1FFY2hffFwOLy0pNOxaPg==" + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -6494,11 +6499,6 @@ "node": ">=8" } }, - "node_modules/shell-quote": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz", - "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==" - }, "node_modules/side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -9476,6 +9476,11 @@ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" }, + "escape-it": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/escape-it/-/escape-it-0.3.0.tgz", + "integrity": "sha512-LgPtY7WCP1wcIgL591yG+01YzstxxZaHS1ZjTnQLFGWG+ZF/cjBIGh1C7uf2xlRlq1FFY2hffFwOLy0pNOxaPg==" + }, "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -11956,11 +11961,6 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, - "shell-quote": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz", - "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==" - }, "side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", diff --git a/package.json b/package.json index fc9d8e11..53aa51ed 100644 --- a/package.json +++ b/package.json @@ -49,9 +49,9 @@ "dependencies": { "chalk": "^4.1.0", "date-fns": "^2.29.1", + "escape-it": "^0.3.0", "lodash": "^4.17.21", "rxjs": "^7.0.0", - "shell-quote": "^1.7.3", "spawn-command": "^0.0.2-1", "supports-color": "^8.1.0", "tree-kill": "^1.2.2", diff --git a/src/command-parser/expand-arguments.spec.ts b/src/command-parser/expand-arguments.spec.ts index 8913ea48..0d3f7f7f 100644 --- a/src/command-parser/expand-arguments.spec.ts +++ b/src/command-parser/expand-arguments.spec.ts @@ -1,6 +1,8 @@ import { CommandInfo } from '../command'; import { ExpandArguments } from './expand-arguments'; +const isWindows = process.platform === 'win32'; + const createCommandInfo = (command: string): CommandInfo => ({ command, name: '', @@ -21,7 +23,10 @@ it('single argument placeholder is replaced', () => { it('argument placeholder is replaced and quoted properly', () => { const parser = new ExpandArguments(['foo bar']); const commandInfo = createCommandInfo('echo {1}'); - expect(parser.parse(commandInfo)).toEqual({ ...commandInfo, command: "echo 'foo bar'" }); + expect(parser.parse(commandInfo)).toEqual({ + ...commandInfo, + command: isWindows ? 'echo "foo bar"' : "echo 'foo bar'", + }); }); it('multiple single argument placeholders are replaced', () => { @@ -57,7 +62,10 @@ it('all arguments placeholder is replaced', () => { it('combined arguments placeholder is replaced', () => { const parser = new ExpandArguments(['foo', 'bar']); const commandInfo = createCommandInfo('echo {*}'); - expect(parser.parse(commandInfo)).toEqual({ ...commandInfo, command: "echo 'foo bar'" }); + expect(parser.parse(commandInfo)).toEqual({ + ...commandInfo, + command: isWindows ? 'echo "foo bar"' : "echo 'foo bar'", + }); }); it('escaped argument placeholders are not replaced', () => { diff --git a/src/command-parser/expand-arguments.ts b/src/command-parser/expand-arguments.ts index 01dfdd8f..380f4e90 100644 --- a/src/command-parser/expand-arguments.ts +++ b/src/command-parser/expand-arguments.ts @@ -1,8 +1,10 @@ -import { quote } from 'shell-quote'; +import escapeFn from 'escape-it'; import { CommandInfo } from '../command'; import { CommandParser } from './command-parser'; +const escape = escapeFn(); + /** * Replace placeholders with additional arguments. */ @@ -22,15 +24,15 @@ export class ExpandArguments implements CommandParser { !isNaN(placeholderTarget) && placeholderTarget <= this.additionalArguments.length ) { - return quote([this.additionalArguments[placeholderTarget - 1]]); + return escape(this.additionalArguments[placeholderTarget - 1]); } // Replace all arguments placeholder. if (placeholderTarget === '@') { - return quote(this.additionalArguments); + return escape(...this.additionalArguments); } // Replace combined arguments placeholder. if (placeholderTarget === '*') { - return quote([this.additionalArguments.join(' ')]); + return escape(this.additionalArguments.join(' ')); } // Replace placeholder with empty string // if value doesn't exist in additional arguments. diff --git a/src/concurrently.spec.ts b/src/concurrently.spec.ts index 0bc0f5d2..0867811c 100644 --- a/src/concurrently.spec.ts +++ b/src/concurrently.spec.ts @@ -7,6 +7,8 @@ import { createFakeProcess, FakeCommand } from './fixtures/fake-command'; import { FlowController } from './flow-control/flow-controller'; import { Logger } from './logger'; +const isWindows = process.platform === 'win32'; + let spawn: SpawnCommand; let kill: KillProcess; let onFinishHooks: (() => void)[]; @@ -237,7 +239,10 @@ it('argument placeholders are properly replaced when additional arguments are pa expect(spawn).toHaveBeenCalledTimes(4); expect(spawn).toHaveBeenCalledWith('echo foo', expect.objectContaining({})); expect(spawn).toHaveBeenCalledWith('echo foo bar', expect.objectContaining({})); - expect(spawn).toHaveBeenCalledWith("echo 'foo bar'", expect.objectContaining({})); + expect(spawn).toHaveBeenCalledWith( + isWindows ? 'echo "foo bar"' : "echo 'foo bar'", + expect.objectContaining({}) + ); expect(spawn).toHaveBeenCalledWith('echo {@}', expect.objectContaining({})); });