Skip to content

Commit

Permalink
add auto colors functionality to resolve #279
Browse files Browse the repository at this point in the history
  • Loading branch information
eliasm307 committed Nov 19, 2021
1 parent 59de6e4 commit c9739e3
Show file tree
Hide file tree
Showing 7 changed files with 234 additions and 53 deletions.
104 changes: 57 additions & 47 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Concurrently

[![Build Status](https://github.com/open-cli-tools/concurrently/workflows/Tests/badge.svg)](https://github.com/open-cli-tools/concurrently/actions?workflow=Tests)
[![Build Status](https://github.com/open-cli-tools/concurrently/workflows/Tests/badge.svg)](https://github.com/open-cli-tools/concurrently/actions?workflow=Tests)
[![Coverage Status](https://coveralls.io/repos/github/open-cli-tools/concurrently/badge.svg?branch=master)](https://coveralls.io/github/open-cli-tools/concurrently?branch=master)

[![NPM Badge](https://nodei.co/npm/concurrently.png?downloads=true)](https://www.npmjs.com/package/concurrently)
Expand All @@ -11,6 +11,7 @@ Like `npm run watch-js & npm run watch-less` but better.
![](docs/demo.gif)

**Table of contents**

- [Concurrently](#concurrently)
- [Why](#why)
- [Install](#install)
Expand All @@ -32,10 +33,10 @@ tired of opening terminals and made **concurrently**.

**Features:**

* Cross platform (including Windows)
* Output is easy to follow with prefixes
* With `--kill-others` switch, all commands are killed if one dies
* Spawns commands with [spawn-command](https://github.com/mmalecki/spawn-command)
- Cross platform (including Windows)
- Output is easy to follow with prefixes
- With `--kill-others` switch, all commands are killed if one dies
- Spawns commands with [spawn-command](https://github.com/mmalecki/spawn-command)

## Install

Expand All @@ -54,6 +55,7 @@ npm install concurrently --save
## Usage

Remember to surround separate commands with quotes:

```bash
concurrently "command1 arg" "command2 arg"
```
Expand Down Expand Up @@ -230,6 +232,7 @@ For more details, visit https://github.com/open-cli-tools/concurrently
```

## Programmatic Usage

concurrently can be used programmatically by using the API documented below:

### `concurrently(commands[, options])`
Expand All @@ -238,34 +241,35 @@ concurrently can be used programmatically by using the API documented below:
with the shape `{ command, name, prefixColor, env, cwd }`.

- `options` (optional): an object containing any of the below:
- `cwd`: the working directory to be used by all commands. Can be overriden per command.
- `cwd`: the working directory to be used by all commands. Can be overriden per command.
Default: `process.cwd()`.
- `defaultInputTarget`: the default input target when reading from `inputStream`.
- `defaultInputTarget`: the default input target when reading from `inputStream`.
Default: `0`.
- `handleInput`: when `true`, reads input from `process.stdin`.
- `inputStream`: a [`Readable` stream](https://nodejs.org/dist/latest-v10.x/docs/api/stream.html#stream_readable_streams)
- `handleInput`: when `true`, reads input from `process.stdin`.
- `inputStream`: a [`Readable` stream](https://nodejs.org/dist/latest-v10.x/docs/api/stream.html#stream_readable_streams)
to read the input from. Should only be used in the rare instance you would like to stream anything other than `process.stdin`. Overrides `handleInput`.
- `pauseInputStreamOnFinish`: by default, pauses the input stream (`process.stdin` when `handleInput` is enabled, or `inputStream` if provided) when all of the processes have finished. If you need to read from the input stream after `concurrently` has finished, set this to `false`. ([#252](https://github.com/kimmobrunfeldt/concurrently/issues/252)).
- `killOthers`: an array of exitting conditions that will cause a process to kill others.
- `pauseInputStreamOnFinish`: by default, pauses the input stream (`process.stdin` when `handleInput` is enabled, or `inputStream` if provided) when all of the processes have finished. If you need to read from the input stream after `concurrently` has finished, set this to `false`. ([#252](https://github.com/kimmobrunfeldt/concurrently/issues/252)).
- `killOthers`: an array of exitting conditions that will cause a process to kill others.
Can contain any of `success` or `failure`.
- `maxProcesses`: how many processes should run at once.
- `outputStream`: a [`Writable` stream](https://nodejs.org/dist/latest-v10.x/docs/api/stream.html#stream_writable_streams)
- `maxProcesses`: how many processes should run at once.
- `outputStream`: a [`Writable` stream](https://nodejs.org/dist/latest-v10.x/docs/api/stream.html#stream_writable_streams)
to write logs to. Default: `process.stdout`.
- `prefix`: the prefix type to use when logging processes output.
Possible values: `index`, `pid`, `time`, `command`, `name`, `none`, or a template (eg `[{time} process: {pid}]`).
Default: the name of the process, or its index if no name is set.
- `prefixColors`: a list of colors as supported by [chalk](https://www.npmjs.com/package/chalk).
If concurrently would run more commands than there are colors, the last color is repeated.
Prefix colors specified per-command take precedence over this list.
- `prefixLength`: how many characters to show when prefixing with `command`. Default: `10`
- `raw`: whether raw mode should be used, meaning strictly process output will
- `prefix`: the prefix type to use when logging processes output.
Possible values: `index`, `pid`, `time`, `command`, `name`, `none`, or a template (eg `[{time} process: {pid}]`).
Default: the name of the process, or its index if no name is set.
- `prefixColors`: a list of colors as supported by [chalk](https://www.npmjs.com/package/chalk).
If concurrently would run more commands than there are colors, the last color is repeated.
Prefix colors specified per-command take precedence over this list.
- `colors`: let colours be selected to vary automatically where not explicitly defined
- `prefixLength`: how many characters to show when prefixing with `command`. Default: `10`
- `raw`: whether raw mode should be used, meaning strictly process output will
be logged, without any prefixes, colouring or extra stuff.
- `successCondition`: the condition to consider the run was successful.
- `successCondition`: the condition to consider the run was successful.
If `first`, only the first process to exit will make up the success of the run; if `last`, the last process that exits will determine whether the run succeeds.
Anything else means all processes should exit successfully.
- `restartTries`: how many attempts to restart a process that dies will be made. Default: `0`.
- `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)
- `restartTries`: how many attempts to restart a process that dies will be made. Default: `0`.
- `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`

> Returns: a `Promise` that resolves if the run was successful (according to `successCondition` option),
Expand All @@ -277,35 +281,41 @@ concurrently can be used programmatically by using the API documented below:
Example:

```js
const concurrently = require('concurrently');
concurrently([
'npm:watch-*',
{ command: 'nodemon', name: 'server' },
{ command: 'deploy', name: 'deploy', env: { PUBLIC_KEY: '...' } },
{ command: 'watch', name: 'watch', cwd: path.resolve(__dirname, 'scripts/watchers')}
], {
prefix: 'name',
killOthers: ['failure', 'success'],
const concurrently = require("concurrently");
concurrently(
[
"npm:watch-*",
{ command: "nodemon", name: "server" },
{ command: "deploy", name: "deploy", env: { PUBLIC_KEY: "..." } },
{
command: "watch",
name: "watch",
cwd: path.resolve(__dirname, "scripts/watchers"),
},
],
{
prefix: "name",
killOthers: ["failure", "success"],
restartTries: 3,
cwd: path.resolve(__dirname, 'scripts'),
}).then(success, failure);
cwd: path.resolve(__dirname, "scripts"),
}
).then(success, failure);
```

## FAQ

* Process exited with code *null*?

From [Node child_process documentation](http://nodejs.org/api/child_process.html#child_process_event_exit), `exit` event:
- Process exited with code _null_?

> This event is emitted after the child process ends. If the process
> terminated normally, code is the final exit code of the process,
> otherwise null. If the process terminated due to receipt of a signal,
> signal is the string name of the signal, otherwise null.
From [Node child_process documentation](http://nodejs.org/api/child_process.html#child_process_event_exit), `exit` event:

> This event is emitted after the child process ends. If the process
> terminated normally, code is the final exit code of the process,
> otherwise null. If the process terminated due to receipt of a signal,
> signal is the string name of the signal, otherwise null.
So *null* means the process didn't terminate normally. This will make **concurrent**
to return non-zero exit code too.
So _null_ means the process didn't terminate normally. This will make **concurrent**
to return non-zero exit code too.

* Does this work with the npm-replacements [yarn](https://github.com/yarnpkg/yarn) or [pnpm](https://pnpm.js.org/)?
- Does this work with the npm-replacements [yarn](https://github.com/yarnpkg/yarn) or [pnpm](https://pnpm.js.org/)?

Yes! In all examples above, you may replace "`npm`" with "`yarn`" or "`pnpm`".
Yes! In all examples above, you may replace "`npm`" with "`yarn`" or "`pnpm`".
6 changes: 6 additions & 0 deletions bin/concurrently.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ const args = yargs
default: defaults.prefixColors,
type: 'string'
},
'color': {
describe: 'Automatically adds varying prefix colors where commands do not have a prefix color defined',
default: defaults.color,
type: 'boolean'
},
'l': {
alias: 'prefix-length',
describe:
Expand Down Expand Up @@ -167,6 +172,7 @@ concurrently(args._.map((command, index) => ({
hide: args.hide.split(','),
prefix: args.prefix,
prefixColors: args.prefixColors.split(','),
color: args.color,
prefixLength: args.prefixLength,
restartDelay: args.restartAfter,
restartTries: args.restartTries,
Expand Down
3 changes: 2 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ module.exports = exports = (commands, options = {}) => {
conditions: options.killOthers
})
],
prefixColors: options.prefixColors || []
prefixColors: options.prefixColors || [],
color: options.color
});
};

Expand Down
8 changes: 4 additions & 4 deletions src/concurrently.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const ExpandNpmShortcut = require('./command-parser/expand-npm-shortcut');
const ExpandNpmWildcard = require('./command-parser/expand-npm-wildcard');

const CompletionListener = require('./completion-listener');
const PrefixColorSelector = require('./prefix-color-selector');

const getSpawnOpts = require('./get-spawn-opts');
const Command = require('./command');
Expand All @@ -26,19 +27,18 @@ module.exports = (commands, options) => {

options = _.defaults(options, defaults);

const prefixColorSelector = new PrefixColorSelector(options);

const commandParsers = [
new StripQuotes(),
new ExpandNpmShortcut(),
new ExpandNpmWildcard()
];

let lastColor = '';
commands = _(commands)
.map(mapToCommandInfo)
.flatMap(command => parseCommand(command, commandParsers))
.map((command, index) => {
// Use documented behaviour of repeating last color when specifying more commands than colors
lastColor = options.prefixColors && options.prefixColors[index] || lastColor;
return new Command(
Object.assign({
index,
Expand All @@ -47,7 +47,7 @@ module.exports = (commands, options) => {
env: command.env,
cwd: command.cwd || options.cwd,
}),
prefixColor: lastColor,
prefixColor: prefixColorSelector.getNextColor(index),
killProcess: options.kill,
spawn: options.spawn,
}, command)
Expand Down
4 changes: 3 additions & 1 deletion src/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,7 @@ module.exports = {
// Refer to https://date-fns.org/v2.0.1/docs/format
timestampFormat: 'yyyy-MM-dd HH:mm:ss.SSS',
// Current working dir passed as option to spawn command. Default: process.cwd()
cwd: undefined
cwd: undefined,
// Adding prefix colors automatically
color: false,
};
70 changes: 70 additions & 0 deletions src/prefix-color-selector.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
const chalk = require('chalk');

module.exports = class PrefixColorSelector {
get ACCEPTABLE_CONSOLE_COLORS() {
// colors picked randomly, can be amended if required
return [
chalk.cyan,
chalk.yellow,
chalk.magenta,
chalk.grey,
chalk.bgBlueBright,
chalk.bgMagenta,
chalk.magentaBright,
chalk.bgBlack,
chalk.bgWhite,
chalk.bgCyan,
chalk.bgGreen,
chalk.bgYellow,
chalk.bgRed,
chalk.bgGreenBright,
chalk.bgGrey,
chalk.blueBright,
]
// filter out duplicates
.filter((chalkColor, index, arr) => {
return arr.indexOf( chalkColor ) === index;
})
.map(chalkColor => chalkColor.bold);
}

constructor(options) {
this.userDefinedPrefixColors = options.prefixColors;
this.canAutoSelectColors = options.color;
}

getNextColor(index) {
const cannotSelectColor = !this.userDefinedPrefixColors && !this.canAutoSelectColors;
if (cannotSelectColor) { return ''; }

const userDefinedColorForCurrentCommand = this.userDefinedPrefixColors && typeof index === 'number' && this.userDefinedPrefixColors[index];

if (!this.canAutoSelectColors) {
// original behaviour
// Use documented behaviour of repeating last color when specifying more commands than colors
this.lastColor = userDefinedColorForCurrentCommand || this.lastColor;
return this.lastColor;
}

// user preference takes priority if defined
if (userDefinedColorForCurrentCommand) {
this.lastColor = userDefinedColorForCurrentCommand;
return userDefinedColorForCurrentCommand;
}

// auto selection requested and no user preference defined, select next auto color
if (!this.autoColors || !this.autoColors.length) { this.refillAutoColors(); }

// prevent consecutive colors from being the same (ie when transitioning from user colours to auto colours)
const nextColor = this.autoColors.shift();

debugger;
this.lastColor = nextColor !== this.lastColor ? nextColor : this.getNextColor();
return this.lastColor;
}

refillAutoColors() {
// make sure auto colors are not empty after refill
this.autoColors = [...this.ACCEPTABLE_CONSOLE_COLORS];
}
};
Loading

0 comments on commit c9739e3

Please sign in to comment.