Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions bin/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,15 @@ describe('exiting conditions', () => {
expect(exit.code).toBe(0);
});

it('strips outer CLI wrapper quotes before running a command', async () => {
const child = run('"echo foo"');
const lines = await child.getLogLines();
const exit = await child.exit;

expect(lines).toContainEqual(expect.stringContaining('foo'));
expect(exit.code).toBe(0);
});

it('is of failure by default when one of the command fails', async () => {
const exit = await run('"echo foo" "exit 1"').exit;

Expand Down
3 changes: 2 additions & 1 deletion bin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { assertDeprecated } from '../lib/assert.js';
import * as defaults from '../lib/defaults.js';
import { concurrently } from '../lib/index.js';
import { castArray, splitOutsideParens } from '../lib/utils.js';
import { normalizeCliCommand } from './normalize-cli-command.js';
import { readPackageJson } from './read-package-json.js';

const version = String(readPackageJson().version);
Expand Down Expand Up @@ -238,7 +239,7 @@ if (!commands.length) {

concurrently(
commands.map((command, index) => ({
command: String(command),
command: normalizeCliCommand(String(command)),
name: names[index],
})),
{
Expand Down
52 changes: 52 additions & 0 deletions bin/normalize-cli-command.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { expect, it } from 'vitest';

import { normalizeCliCommand } from './normalize-cli-command.js';

it('strips outer CLI wrapper double quotes', () => {
expect(normalizeCliCommand('"echo foo"')).toBe('echo foo');
});

it('strips outer CLI wrapper single quotes', () => {
expect(normalizeCliCommand("'echo foo'")).toBe('echo foo');
});

it('strips quotes around a single wrapped token', () => {
expect(normalizeCliCommand('"echo"')).toBe('echo');
expect(normalizeCliCommand("'echo'")).toBe('echo');
});

it('preserves quotes in well-formed shell commands', () => {
expect(normalizeCliCommand('"/usr/local/bin/mytool" --flag "some value"')).toBe(
'"/usr/local/bin/mytool" --flag "some value"',
);
});

it('preserves well-formed shell commands with multiple quote sets', () => {
expect(
normalizeCliCommand('"/usr/local/bin/mytool" --flag "some value" --other "last arg"'),
).toBe('"/usr/local/bin/mytool" --flag "some value" --other "last arg"');
});

it('preserves single quotes in well-formed shell commands', () => {
expect(normalizeCliCommand("'printf' '%s %s' foo bar")).toBe("'printf' '%s %s' foo bar");
});

it('returns unquoted input unchanged', () => {
expect(normalizeCliCommand('echo foo')).toBe('echo foo');
});

it('returns an empty string unchanged', () => {
expect(normalizeCliCommand('')).toBe('');
});

it('leaves ambiguous input unchanged', () => {
expect(normalizeCliCommand('"echo foo')).toBe('"echo foo');
});

it('leaves input with an unclosed single quote unchanged', () => {
expect(normalizeCliCommand("echo foo'")).toBe("echo foo'");
});

it('leaves input with mismatched quote types unchanged', () => {
expect(normalizeCliCommand('"echo foo\'')).toBe('"echo foo\'');
});
18 changes: 18 additions & 0 deletions bin/normalize-cli-command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export function normalizeCliCommand(command: string): string {
if (command.length < 2) {
return command;
}

const quote = command[0];
const last = command[command.length - 1];
if ((quote !== '"' && quote !== "'") || last !== quote) {
return command;
}

const inner = command.slice(1, -1);
if (inner.includes(quote)) {
return command;
}

return inner;
}
23 changes: 19 additions & 4 deletions lib/concurrently.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,13 +136,28 @@ it('does not spawn further commands on abort signal aborted', () => {
expect(spawn).toHaveBeenCalledTimes(1);
});

it('runs controllers with the commands', () => {
create(['echo', '"echo wrapped"']);
it('preserves quotes in well-formed shell commands in the library API', () => {
create(['"/usr/local/bin/mytool" --flag "some value"']);

controllers.forEach((controller) => {
expect(controller.handle).toHaveBeenCalledWith([
expect.objectContaining({ command: 'echo', index: 0 }),
expect.objectContaining({ command: 'echo wrapped', index: 1 }),
expect.objectContaining({
command: '"/usr/local/bin/mytool" --flag "some value"',
index: 0,
}),
]);
});
});

it('passes commands with multiple quote sets through unchanged in the library API', () => {
create(['"/usr/local/bin/mytool" --flag "some value" --other "last arg"']);

controllers.forEach((controller) => {
expect(controller.handle).toHaveBeenCalledWith([
expect.objectContaining({
command: '"/usr/local/bin/mytool" --flag "some value" --other "last arg"',
index: 0,
}),
]);
});
});
Expand Down
7 changes: 1 addition & 6 deletions lib/concurrently.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import { CommandParser } from './command-parser/command-parser.js';
import { ExpandArguments } from './command-parser/expand-arguments.js';
import { ExpandShortcut } from './command-parser/expand-shortcut.js';
import { ExpandWildcard } from './command-parser/expand-wildcard.js';
import { StripQuotes } from './command-parser/strip-quotes.js';
import { CompletionListener, SuccessCondition } from './completion-listener.js';
import { FlowController } from './flow-control/flow-controller.js';
import { Logger } from './logger.js';
Expand Down Expand Up @@ -170,11 +169,7 @@ export function concurrently(

const prefixColorSelector = new PrefixColorSelector(options.prefixColors || []);

const commandParsers: CommandParser[] = [
new StripQuotes(),
new ExpandShortcut(),
new ExpandWildcard(),
];
const commandParsers: CommandParser[] = [new ExpandShortcut(), new ExpandWildcard()];

if (options.additionalArguments) {
commandParsers.push(new ExpandArguments(options.additionalArguments));
Expand Down
Loading