Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support passthrough of additional arguments to commands via placeholders #307

Merged
merged 15 commits into from May 9, 2022
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 5 additions & 4 deletions .editorconfig
Expand Up @@ -3,16 +3,17 @@
# top-most EditorConfig file
root = true

# Unix-style newlines with a newline ending every file
# Unix-style newlines with a newline ending and space indentation for all files
[*]
end_of_line = lf
insert_final_newline = true
indent_style = space

# Tab indentation (no size specified)
[*.js]
# 4 space indentation and max line length of 100 in *.ts files
[*.ts]
indent_size = 4
max_line_length = 100

# Matches the exact files package.json
# 2 space indentation in package.json
[package.json]
indent_size = 2
76 changes: 50 additions & 26 deletions README.md
Expand Up @@ -134,33 +134,37 @@ Good frontend one-liner example [here](https://github.com/kimmobrunfeldt/dont-co
Help:

```

concurrently [options] <command ...>

General
-m, --max-processes How many processes should run at once.
New processes only spawn after all restart tries of a
process. [number]
-n, --names List of custom names to be used in prefix template.
Example names: "main,browser,server" [string]
--name-separator The character to split <names> on. Example usage:
concurrently -n "styles|scripts|server" --name-separator
"|" [default: ","]
-r, --raw Output only raw output of processes, disables
prettifying and concurrently coloring. [boolean]
-s, --success Return exit code of zero or one based on the success or
failure of the "first" child to terminate, the "last
child", or succeed only if "all" child processes
succeed.
-m, --max-processes How many processes should run at once.
New processes only spawn after all restart tries
of a process. [number]
-n, --names List of custom names to be used in prefix
template.
Example names: "main,browser,server" [string]
--name-separator The character to split <names> on. Example usage:
concurrently -n "styles|scripts|server"
--name-separator "|" [default: ","]
-s, --success Return exit code of zero or one based on the
success or failure of the "first" child to
terminate, the "last child", or succeed only if
"all" child processes succeed.
[choices: "first", "last", "all"] [default: "all"]
--no-color Disables colors from logging [boolean]
--hide Comma-separated list of processes to hide the output.
The processes can be identified by their name or index.
[string] [default: ""]
-g, --group Order the output as if the commands were run
sequentially. [boolean]
--timings Show timing information for all processes
-r, --raw Output only raw output of processes, disables
prettifying and concurrently coloring. [boolean]
--no-color Disables colors from logging. [boolean]
--hide Comma-separated list of processes to hide the
output.
The processes can be identified by their name or
index. [string] [default: ""]
-g, --group Order the output as if the commands were run
sequentially. [boolean]
--timings Show timing information for all processes.
[boolean] [default: false]
-P, --passthrough-arguments Passthrough additional arguments to commands
(accessible via placeholders) instead of treating
them as commands. [boolean] [default: false]

Prefix styling
-p, --prefix Prefix used in logging for each process.
Expand Down Expand Up @@ -197,9 +201,9 @@ Input handling
process. [default: 0]

Killing other processes
-k, --kill-others kill other processes if one exits or dies [boolean]
--kill-others-on-fail kill other processes if one exits with non zero
status code [boolean]
-k, --kill-others Kill other processes if one exits or dies.[boolean]
--kill-others-on-fail Kill other processes if one exits with non zero
status code. [boolean]

Restarting
--restart-tries How many times a process that died should restart.
Expand All @@ -212,6 +216,7 @@ Options:
-h, --help Show help [boolean]
-v, -V, --version Show version number [boolean]


Examples:

- Output nothing more than stdout+stderr of child processes
Expand All @@ -233,7 +238,8 @@ Examples:

- Configuring via environment variables with CONCURRENTLY_ prefix

$ CONCURRENTLY_RAW=true CONCURRENTLY_KILL_OTHERS=true concurrently "echo hello" "echo world"
$ CONCURRENTLY_RAW=true CONCURRENTLY_KILL_OTHERS=true concurrently "echo
hello" "echo world"

- Send input to default

Expand All @@ -258,6 +264,23 @@ Examples:

$ concurrently "npm:watch-*"

- Exclude patterns so that between "lint:js" and "lint:fix:js", only "lint:js"
is ran

$ concurrently "npm:*(!fix)"

- Passthrough some additional arguments via '{<number>}' placeholder

$ concurrently -P "echo {1}" -- foo

- Passthrough all additional arguments via '{@}' placeholder

$ concurrently -P "npm:dev-* -- {@}" -- --watch --noEmit

- Passthrough all additional arguments combined via '{*}' placeholder

$ concurrently -P "npm:dev-* -- {*}" -- --watch --noEmit

For more details, visit https://github.com/open-cli-tools/concurrently
```

Expand Down Expand Up @@ -299,6 +322,7 @@ concurrently can be used programmatically by using the API documented below:
- `restartDelay`: how many milliseconds to wait between process restarts. Default: `0`.
- `timestampFormat`: a [date-fns format](https://date-fns.org/v2.0.1/docs/format)
to use when prefixing with `time`. Default: `yyyy-MM-dd HH:mm:ss.ZZZ`
- `additionalArguments`: list of additional arguments passed that will get replaced in each command. If not defined, no argument replacing will happen.

> **Returns:** an object in the shape `{ result, commands }`.
> - `result`: a `Promise` that resolves if the run was successful (according to `successCondition` option),
Expand Down
23 changes: 21 additions & 2 deletions bin/concurrently.spec.ts
Expand Up @@ -58,11 +58,11 @@ it('has help command', done => {
});

it('has version command', done => {
Rx.combineLatest(
Rx.combineLatest([
run('--version').close,
run('-V').close,
run('-v').close,
).subscribe(events => {
]).subscribe(events => {
expect(events[0][0]).toBe(0);
expect(events[1][0]).toBe(0);
expect(events[2][0]).toBe(0);
Expand Down Expand Up @@ -446,3 +446,22 @@ describe('--timings', () => {
}, done);
});
});

describe('--passthrough-arguments', () => {
it('argument placeholders are properly replaced when passthrough-arguments is enabled', done => {
const child = run('--passthrough-arguments "echo {1}" -- echo');
child.log.pipe(buffer(child.close)).subscribe(lines => {
expect(lines).toContainEqual(expect.stringContaining('[0] echo echo exited with code 0'));
done();
}, done);
});

it('argument placeholders are not replaced when passthrough-arguments is disabled', done => {
const child = run('"echo {1}" -- echo');
child.log.pipe(buffer(child.close)).subscribe(lines => {
expect(lines).toContainEqual(expect.stringContaining('[0] echo {1} exited with code 0'));
expect(lines).toContainEqual(expect.stringContaining('[1] echo exited with code 0'));
done();
}, done);
});
});
81 changes: 53 additions & 28 deletions bin/concurrently.ts
@@ -1,10 +1,20 @@
#!/usr/bin/env node
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import * as defaults from '../src/defaults';
import concurrently from '../src/index';
import { epilogue } from './epilogue';

const args = yargs
// Clean-up arguments (yargs expects only the arguments after the program name)
const cleanArgs = hideBin(process.argv);
// Find argument separator (double dash)
const argsSepIdx = cleanArgs.findIndex((arg) => arg === '--');
// Arguments before separator
const argsBeforeSep = argsSepIdx >= 0 ? cleanArgs.slice(0, argsSepIdx) : cleanArgs;
// Arguments after separator
const argsAfterSep = argsSepIdx >= 0 ? cleanArgs.slice(argsSepIdx + 1) : [];

const args = yargs(argsBeforeSep)
.usage('$0 [options] <command ...>')
.help('h')
.alias('h', 'help')
Expand Down Expand Up @@ -70,19 +80,27 @@ const args = yargs
type: 'boolean',
},
'timings': {
describe: 'Show timing information for all processes',
describe: 'Show timing information for all processes.',
type: 'boolean',
default: defaults.timings,
},
'passthrough-arguments': {
alias: 'P',
describe:
'Passthrough additional arguments to commands (accessible via placeholders) ' +
'instead of treating them as commands.',
type: 'boolean',
default: defaults.passthroughArguments,
},

// Kill others
'kill-others': {
alias: 'k',
describe: 'kill other processes if one exits or dies',
describe: 'Kill other processes if one exits or dies.',
type: 'boolean',
},
'kill-others-on-fail': {
describe: 'kill other processes if one exits with non zero status code',
describe: 'Kill other processes if one exits with non zero status code.',
type: 'boolean',
},

Expand Down Expand Up @@ -154,38 +172,45 @@ const args = yargs
'Can be either the index or the name of the process.',
},
})
.group(['m', 'n', 'name-separator', 'raw', 's', 'no-color', 'hide', 'group', 'timings'], 'General')
.group(['m', 'n', 'name-separator', 's', 'r', 'no-color', 'hide', 'g', 'timings', 'P'], 'General')
.group(['p', 'c', 'l', 't'], 'Prefix styling')
.group(['i', 'default-input-target'], 'Input handling')
.group(['k', 'kill-others-on-fail'], 'Killing other processes')
.group(['restart-tries', 'restart-after'], 'Restarting')
.epilogue(epilogue)
.argv;
.parseSync();

// Get names of commands by the specified separator
const names = (args.names || '').split(args['name-separator']);
// If "passthrough-arguments" is disabled, treat additional arguments as commands
const commands = args.passthroughArguments ? args._ : [...args._, ...argsAfterSep];

concurrently(args._.map((command, index) => ({
command: String(command),
name: names[index],
})), {
handleInput: args['handle-input'],
defaultInputTarget: args['default-input-target'],
killOthers: args.killOthers
? ['success', 'failure']
: (args.killOthersOnFail ? ['failure'] : []),
maxProcesses: args['max-processes'],
raw: args.raw,
hide: args.hide.split(','),
group: args.group,
prefix: args.prefix,
prefixColors: args['prefix-colors'].split(','),
prefixLength: args['prefix-length'],
restartDelay: args['restart-after'],
restartTries: args['restart-tries'],
successCondition: args.success,
timestampFormat: args['timestamp-format'],
timings: args.timings,
}).result.then(
concurrently(
commands.map((command, index) => ({
command: String(command),
name: names[index],
})),
{
handleInput: args['handle-input'],
defaultInputTarget: args['default-input-target'],
killOthers: args.killOthers
? ['success', 'failure']
: (args.killOthersOnFail ? ['failure'] : []),
maxProcesses: args['max-processes'],
raw: args.raw,
hide: args.hide.split(','),
group: args.group,
prefix: args.prefix,
prefixColors: args['prefix-colors'].split(','),
prefixLength: args['prefix-length'],
restartDelay: args['restart-after'],
restartTries: args['restart-tries'],
successCondition: args.success,
timestampFormat: args['timestamp-format'],
timings: args.timings,
additionalArguments: args.passthroughArguments ? argsAfterSep : undefined,
},
).result.then(
() => process.exit(0),
() => process.exit(1),
);
12 changes: 12 additions & 0 deletions bin/epilogue.ts
Expand Up @@ -54,6 +54,18 @@ const examples = [
description: 'Exclude patterns so that between "lint:js" and "lint:fix:js", only "lint:js" is ran',
example: '$ $0 "npm:*(!fix)"',
},
{
description: 'Passthrough some additional arguments via \'{<number>}\' placeholder',
example: '$ $0 -P "echo {1}" -- foo',
},
{
description: 'Passthrough all additional arguments via \'{@}\' placeholder',
example: '$ $0 -P "npm:dev-* -- {@}" -- --watch --noEmit',
},
{
description: 'Passthrough all additional arguments combined via \'{*}\' placeholder',
example: '$ $0 -P "npm:dev-* -- {*}" -- --watch --noEmit',
},
];

export const epilogue = `
Expand Down