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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ Check out documentation and other usage examples in the [`docs` directory](./doc
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 or a string as supported by [Chalk](https://www.npmjs.com/package/chalk) and additional style `auto` for an automatically picked color.
Supports all Chalk color functions: `#RRGGBB`, `bg#RRGGBB`, `hex()`, `bgHex()`, `rgb()`, `bgRgb()`, `ansi256()`, `bgAnsi256()`.
Functions and modifiers can be chained (e.g., `rgb(255,136,0).bold`, `black.bgHex(#00FF00).dim`).
If concurrently would run more commands than there are colors, the last color is repeated, unless if the last color value is `auto` which means following colors are automatically picked to vary.
Prefix colors specified per-command take precedence over this list.
- `prefixLength`: how many characters to show when prefixing with `command`. Default: `10`
Expand Down
24 changes: 23 additions & 1 deletion bin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,28 @@ import { concurrently } from '../lib/index.js';
import { castArray } from '../lib/utils.js';
import { readPackageJson } from './read-package-json.js';

/**
* Splits a color arguments string on commas, but preserves commas inside parentheses.
* e.g., "red,rgb(255,255,0),blue" → ["red", "rgb(255,255,0)", "blue"]
*/
function splitColorArgs(input: string): string[] {
const colors: string[] = [];
let current = '';
let parenDepth = 0;
for (const char of input) {
if (char === '(') parenDepth++;
if (char === ')') parenDepth--;
if (char === ',' && parenDepth === 0) {
if (current.trim()) colors.push(current.trim());
current = '';
} else {
current += char;
}
}
if (current.trim()) colors.push(current.trim());
return colors;
}

const version = String(readPackageJson().version);
const epilogue = `For documentation and more examples, visit:\nhttps://github.com/open-cli-tools/concurrently/tree/v${version}/docs`;

Expand Down Expand Up @@ -256,7 +278,7 @@ concurrently(
hide: args.hide.split(','),
group: args.group,
prefix: args.prefix,
prefixColors: args.prefixColors.split(','),
prefixColors: splitColorArgs(args.prefixColors),
prefixLength: args.prefixLength,
padPrefix: args.padPrefix,
restartDelay:
Expand Down
28 changes: 28 additions & 0 deletions docs/cli/prefixing.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,34 @@ $ concurrently -c bgGray,red.bgBlack 'echo Hello there' 'echo General Kenobi!'
- `bgYellow`
</details>

### Advanced Color Functions

concurrently supports all [Chalk color functions](https://github.com/chalk/chalk#256-and-truecolor-color-support):

| Function | Description |
| ---------------- | --------------------------- |
| `#RRGGBB` | Foreground hex (shorthand) |
| `bg#RRGGBB` | Background hex (shorthand) |
| `hex(#RRGGBB)` | Foreground hex |
| `bgHex(#RRGGBB)` | Background hex |
| `rgb(R,G,B)` | Foreground RGB (0-255) |
| `bgRgb(R,G,B)` | Background RGB (0-255) |
| `ansi256(N)` | Foreground ANSI 256 (0-255) |
| `bgAnsi256(N)` | Background ANSI 256 (0-255) |

All functions can be chained with colors and modifiers:

```bash
# Hex colors
$ concurrently -c 'bg#FF0000.bold,black.bgHex(#00FF00).dim' 'echo Red bg' 'echo Green bg'

# RGB colors
$ concurrently -c 'rgb(255,136,0).bold,black.bgRgb(100,100,255)' 'echo Orange' 'echo Blue bg'

# ANSI 256 colors
$ concurrently -c 'ansi256(199),ansi256(50).bgAnsi256(17)' 'echo Pink' 'echo Cyan on blue'
```

## Prefix Length

When using the `command` prefix style, it's possible that it'll be too long.<br/>
Expand Down
202 changes: 202 additions & 0 deletions lib/logger.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,208 @@ describe('#logCommandText()', () => {
);
});

it('logs prefix using prefixColor from command if prefixColor is a bg hex value (short form)', () => {
const { logger } = createLogger({});
const cmd = new FakeCommand('', undefined, 1, {
prefixColor: 'bg#32bd8a',
});
logger.logCommandText('foo', cmd);

expect(logger.log).toHaveBeenCalledWith(`${chalk.bgHex('#32bd8a')('[1]')} `, 'foo', cmd);
});

it('logs prefix using prefixColor from command if prefixColor is a bg hex value with modifiers (short form)', () => {
const { logger } = createLogger({});
const cmd = new FakeCommand('', undefined, 1, {
prefixColor: 'bg#32bd8a.bold',
});
logger.logCommandText('foo', cmd);

expect(logger.log).toHaveBeenCalledWith(
`${chalk.bgHex('#32bd8a').bold('[1]')} `,
'foo',
cmd,
);
});

it('handles 3-digit hex codes for bg hex (short form)', () => {
const { logger } = createLogger({});
const cmd = new FakeCommand('', undefined, 1, {
prefixColor: 'bg#f00',
});
logger.logCommandText('foo', cmd);

expect(logger.log).toHaveBeenCalledWith(`${chalk.bgHex('#f00')('[1]')} `, 'foo', cmd);
});

it('logs prefix using prefixColor from command if prefixColor is a bgHex() value (explicit form)', () => {
const { logger } = createLogger({});
const cmd = new FakeCommand('', undefined, 1, {
prefixColor: 'bgHex(#ff5500)',
});
logger.logCommandText('foo', cmd);

expect(logger.log).toHaveBeenCalledWith(`${chalk.bgHex('#ff5500')('[1]')} `, 'foo', cmd);
});

it('logs prefix using prefixColor from command if prefixColor is a bgHex() value with modifiers (explicit form)', () => {
const { logger } = createLogger({});
const cmd = new FakeCommand('', undefined, 1, {
prefixColor: 'bgHex(#ff5500).dim',
});
logger.logCommandText('foo', cmd);

expect(logger.log).toHaveBeenCalledWith(
`${chalk.bgHex('#ff5500').dim('[1]')} `,
'foo',
cmd,
);
});

it('handles 3-digit hex codes for bgHex() (explicit form)', () => {
const { logger } = createLogger({});
const cmd = new FakeCommand('', undefined, 1, {
prefixColor: 'bgHex(#0f0)',
});
logger.logCommandText('foo', cmd);

expect(logger.log).toHaveBeenCalledWith(`${chalk.bgHex('#0f0')('[1]')} `, 'foo', cmd);
});

it('falls back to default color for malformed bgHex() syntax', () => {
const { logger } = createLogger({});
const cmd = new FakeCommand('', undefined, 1, {
prefixColor: 'bgHex(invalid)',
});
logger.logCommandText('foo', cmd);

expect(logger.log).toHaveBeenCalledWith(`${chalk.reset('[1]')} `, 'foo', cmd);
});

it('logs prefix with chained fgColor.bgHex().modifier pattern', () => {
const { logger } = createLogger({});
const cmd = new FakeCommand('', undefined, 1, {
prefixColor: 'black.bgHex(#533AFD).dim',
});
logger.logCommandText('foo', cmd);

expect(logger.log).toHaveBeenCalledWith(
`${chalk.black.bgHex('#533AFD').dim('[1]')} `,
'foo',
cmd,
);
});

it('logs prefix with chained fgColor.bg#HEXCODE.modifier pattern', () => {
const { logger } = createLogger({});
const cmd = new FakeCommand('', undefined, 1, {
prefixColor: 'black.bg#FF0000.bold',
});
logger.logCommandText('foo', cmd);

expect(logger.log).toHaveBeenCalledWith(
`${chalk.black.bgHex('#FF0000').bold('[1]')} `,
'foo',
cmd,
);
});

it('logs prefix with chained #HEXCODE.bgNamed.modifier pattern', () => {
const { logger } = createLogger({});
const cmd = new FakeCommand('', undefined, 1, {
prefixColor: '#FF0000.bgBlue.dim',
});
logger.logCommandText('foo', cmd);

expect(logger.log).toHaveBeenCalledWith(
`${chalk.hex('#FF0000').bgBlue.dim('[1]')} `,
'foo',
cmd,
);
});

it('logs prefix using rgb() color function', () => {
const { logger } = createLogger({});
const cmd = new FakeCommand('', undefined, 1, {
prefixColor: 'rgb(255,136,0).bold',
});
logger.logCommandText('foo', cmd);

expect(logger.log).toHaveBeenCalledWith(
`${chalk.rgb(255, 136, 0).bold('[1]')} `,
'foo',
cmd,
);
});

it('logs prefix using bgRgb() color function', () => {
const { logger } = createLogger({});
const cmd = new FakeCommand('', undefined, 1, {
prefixColor: 'black.bgRgb(100,100,255)',
});
logger.logCommandText('foo', cmd);

expect(logger.log).toHaveBeenCalledWith(
`${chalk.black.bgRgb(100, 100, 255)('[1]')} `,
'foo',
cmd,
);
});

it('logs prefix using ansi256() color function', () => {
const { logger } = createLogger({});
const cmd = new FakeCommand('', undefined, 1, {
prefixColor: 'ansi256(199)',
});
logger.logCommandText('foo', cmd);

expect(logger.log).toHaveBeenCalledWith(`${chalk.ansi256(199)('[1]')} `, 'foo', cmd);
});

it('logs prefix using bgAnsi256() color function', () => {
const { logger } = createLogger({});
const cmd = new FakeCommand('', undefined, 1, {
prefixColor: 'ansi256(199).bgAnsi256(50)',
});
logger.logCommandText('foo', cmd);

expect(logger.log).toHaveBeenCalledWith(
`${chalk.ansi256(199).bgAnsi256(50)('[1]')} `,
'foo',
cmd,
);
});

it('logs prefix using hex() explicit function', () => {
const { logger } = createLogger({});
const cmd = new FakeCommand('', undefined, 1, {
prefixColor: 'hex(#ff5500)',
});
logger.logCommandText('foo', cmd);

expect(logger.log).toHaveBeenCalledWith(`${chalk.hex('#ff5500')('[1]')} `, 'foo', cmd);
});

it('falls back to default color for malformed hex() syntax', () => {
const { logger } = createLogger({});
const cmd = new FakeCommand('', undefined, 1, {
prefixColor: 'hex(invalid)',
});
logger.logCommandText('foo', cmd);

expect(logger.log).toHaveBeenCalledWith(`${chalk.reset('[1]')} `, 'foo', cmd);
});

it('falls back to default color for unknown function name', () => {
const { logger } = createLogger({});
const cmd = new FakeCommand('', undefined, 1, {
prefixColor: 'unknownFunc(123)',
});
logger.logCommandText('foo', cmd);

expect(logger.log).toHaveBeenCalledWith(`${chalk.reset('[1]')} `, 'foo', cmd);
});

it('does nothing if command is hidden by name', () => {
const { logger } = createLogger({ hide: ['abc'] });
const cmd = new FakeCommand('abc');
Expand Down
Loading