Skip to content

Commit d0a082b

Browse files
Merge remote-tracking branch 'upstream/main' into fix/normalize-cli-command-quotes
2 parents 4213f66 + 6129083 commit d0a082b

14 files changed

Lines changed: 946 additions & 609 deletions

File tree

.github/actions/setup/action.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,15 @@ runs:
2121
package-manager-cache: false
2222

2323
- name: Install pnpm
24-
uses: pnpm/action-setup@v4
24+
uses: pnpm/action-setup@v6
2525

2626
- name: Get pnpm store directory
2727
id: pnpm-cache-dir
2828
shell: bash
2929
run: echo "dir=$(pnpm store path)" >> $GITHUB_OUTPUT
3030

3131
- name: Setup pnpm cache
32-
uses: actions/cache@v4
32+
uses: actions/cache@v5
3333
with:
3434
path: ${{ steps.pnpm-cache-dir.outputs.dir }}
3535
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
[![Latest Release](https://img.shields.io/github/v/release/open-cli-tools/concurrently?label=Release)](https://github.com/open-cli-tools/concurrently/releases)
44
[![License](https://img.shields.io/github/license/open-cli-tools/concurrently?label=License)](https://github.com/open-cli-tools/concurrently/blob/main/LICENSE)
55
[![Weekly Downloads on NPM](https://img.shields.io/npm/dw/concurrently?label=Downloads&logo=npm)](https://www.npmjs.com/package/concurrently)
6-
[![CI Status](https://img.shields.io/github/actions/workflow/status/open-cli-tools/concurrently/test.yml?label=CI&logo=github)](https://github.com/open-cli-tools/concurrently/actions/workflows/test.yml)
6+
[![CI Status](https://img.shields.io/github/actions/workflow/status/open-cli-tools/concurrently/ci.yml?label=CI&logo=github)](https://github.com/open-cli-tools/concurrently/actions/workflows/ci.yml)
77
[![Coverage Status](https://img.shields.io/coveralls/github/open-cli-tools/concurrently/main?label=Coverage&logo=coveralls)](https://coveralls.io/github/open-cli-tools/concurrently?branch=main)
88

99
Run multiple commands concurrently.
@@ -112,6 +112,8 @@ Check out documentation and other usage examples in the [`docs` directory](./doc
112112
Possible values: `index`, `pid`, `time`, `command`, `name`, `none`, or a template (eg `[{time} process: {pid}]`).
113113
Default: the name of the process, or its index if no name is set.
114114
- `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.
115+
Supports all Chalk color functions: `#RRGGBB`, `bg#RRGGBB`, `hex()`, `bgHex()`, `rgb()`, `bgRgb()`, `ansi256()`, `bgAnsi256()`.
116+
Functions and modifiers can be chained (e.g., `rgb(255,136,0).bold`, `black.bgHex(#00FF00).dim`).
115117
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.
116118
Prefix colors specified per-command take precedence over this list.
117119
- `prefixLength`: how many characters to show when prefixing with `command`. Default: `10`

bin/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { hideBin } from 'yargs/helpers';
77
import { assertDeprecated } from '../lib/assert.js';
88
import * as defaults from '../lib/defaults.js';
99
import { concurrently } from '../lib/index.js';
10-
import { castArray } from '../lib/utils.js';
10+
import { castArray, splitOutsideParens } from '../lib/utils.js';
1111
import { normalizeCliCommand } from './normalize-cli-command.js';
1212
import { readPackageJson } from './read-package-json.js';
1313

@@ -257,7 +257,7 @@ concurrently(
257257
hide: args.hide.split(','),
258258
group: args.group,
259259
prefix: args.prefix,
260-
prefixColors: args.prefixColors.split(','),
260+
prefixColors: splitOutsideParens(args.prefixColors, ','),
261261
prefixLength: args.prefixLength,
262262
padPrefix: args.padPrefix,
263263
restartDelay:

docs/cli/prefixing.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,34 @@ $ concurrently -c bgGray,red.bgBlack 'echo Hello there' 'echo General Kenobi!'
118118
- `bgYellow`
119119
</details>
120120

121+
### Advanced Color Functions
122+
123+
concurrently supports all [Chalk color functions](https://github.com/chalk/chalk#256-and-truecolor-color-support):
124+
125+
| Function | Description |
126+
| ---------------- | --------------------------- |
127+
| `#RRGGBB` | Foreground hex (shorthand) |
128+
| `bg#RRGGBB` | Background hex (shorthand) |
129+
| `hex(#RRGGBB)` | Foreground hex |
130+
| `bgHex(#RRGGBB)` | Background hex |
131+
| `rgb(R,G,B)` | Foreground RGB (0-255) |
132+
| `bgRgb(R,G,B)` | Background RGB (0-255) |
133+
| `ansi256(N)` | Foreground ANSI 256 (0-255) |
134+
| `bgAnsi256(N)` | Background ANSI 256 (0-255) |
135+
136+
All functions can be chained with colors and modifiers:
137+
138+
```bash
139+
# Hex colors
140+
$ concurrently -c 'bg#FF0000.bold,black.bgHex(#00FF00).dim' 'echo Red bg' 'echo Green bg'
141+
142+
# RGB colors
143+
$ concurrently -c 'rgb(255,136,0).bold,black.bgRgb(100,100,255)' 'echo Orange' 'echo Blue bg'
144+
145+
# ANSI 256 colors
146+
$ concurrently -c 'ansi256(199),ansi256(50).bgAnsi256(17)' 'echo Pink' 'echo Cyan on blue'
147+
```
148+
121149
## Prefix Length
122150

123151
When using the `command` prefix style, it's possible that it'll be too long.<br/>

lib/assert.spec.ts

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
1-
import { afterEach, describe, expect, it, vi } from 'vitest';
1+
import { afterEach, beforeEach, describe, expect, it, MockInstance, vi } from 'vitest';
22

3-
import { assertDeprecated } from './assert.js';
3+
import { assertDeprecated, assertNotRuntime } from './assert.js';
44

5-
describe('#assertDeprecated()', () => {
6-
const consoleMock = vi.spyOn(console, 'warn').mockImplementation(() => {});
5+
let consoleMock: MockInstance;
6+
beforeEach(() => {
7+
consoleMock = vi.spyOn(console, 'warn').mockImplementation(() => {});
8+
});
79

8-
afterEach(() => {
9-
vi.clearAllMocks();
10-
});
10+
afterEach(() => {
11+
vi.clearAllMocks();
12+
});
1113

14+
describe('assertDeprecated()', () => {
1215
it('prints warning with name and message when condition is false', () => {
1316
assertDeprecated(false, 'example-flag', 'This is an example message.');
1417

@@ -33,3 +36,29 @@ describe('#assertDeprecated()', () => {
3336
expect(consoleMock).not.toHaveBeenCalled();
3437
});
3538
});
39+
40+
describe('assertNotRuntime()', () => {
41+
it('prints warning with name and message when condition is false', () => {
42+
assertNotRuntime(false, 'example-flag', 'This is an example message.');
43+
44+
expect(consoleMock).toHaveBeenLastCalledWith(
45+
'[concurrently] Running via example-flag is not well supported. This is an example message.',
46+
);
47+
});
48+
49+
it('prints same warning only once', () => {
50+
assertNotRuntime(false, 'example-flag', 'This is an example message.');
51+
assertNotRuntime(false, 'different-flag', 'This is another message.');
52+
53+
expect(consoleMock).toBeCalledTimes(1);
54+
expect(consoleMock).toHaveBeenLastCalledWith(
55+
'[concurrently] Running via different-flag is not well supported. This is another message.',
56+
);
57+
});
58+
59+
it('prints nothing if condition is true', () => {
60+
assertNotRuntime(true, 'example-flag', 'This is an example message.');
61+
62+
expect(consoleMock).not.toHaveBeenCalled();
63+
});
64+
});

lib/assert.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,17 @@ export function assertDeprecated(check: boolean, name: string, message: string)
1111
deprecations.add(name);
1212
}
1313
}
14+
15+
const runtimes = new Set<string>();
16+
17+
/**
18+
* Asserts that some condition is true, and if not, prints a warning about the runtime not being well supported.
19+
* The message is printed only once.
20+
*/
21+
export function assertNotRuntime(check: boolean, name: string, message: string) {
22+
if (!check && !runtimes.has(name)) {
23+
// eslint-disable-next-line no-console
24+
console.warn(`[concurrently] Running via ${name} is not well supported. ${message}`);
25+
runtimes.add(name);
26+
}
27+
}

lib/completion-listener.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ describe('detect commands exit conditions', () => {
255255
return expect(result).rejects.toEqual(expect.anything());
256256
});
257257

258-
it(`fails if some commands ${nameOrIndex} exit with non-0 code`, () => {
258+
it(`succeeds if command ${nameOrIndex} exits with code 0 even when others fail`, () => {
259259
const result = createController(condition).listen(commands);
260260

261261
emitFakeCloseEvent(commands[0], { exitCode: 1 });

lib/index.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import process from 'node:process';
22
import { Readable } from 'node:stream';
33

4-
import { assertDeprecated } from './assert.js';
4+
import { assertDeprecated, assertNotRuntime } from './assert.js';
55
import { CloseEvent, Command, CommandIdentifier, TimerEvent } from './command.js';
66
import {
77
concurrently as createConcurrently,
@@ -127,6 +127,14 @@ export function concurrently(
127127
options: Partial<ConcurrentlyOptions> = {},
128128
) {
129129
assertDeprecated(options.killOthers === undefined, 'killOthers', 'Use killOthersOn instead.');
130+
assertNotRuntime(
131+
// When run via /snap/bin/node, process.execPath maps to the actual snap path, but it also sets
132+
// several SNAP_* env variables. If the snap is run directly via e.g. /snap/node/current/bin/node,
133+
// the issues don't happen and no env variables are set.
134+
!String(process.env.SNAP).startsWith('/snap'),
135+
'Snap',
136+
'Snap confinement can interfere with spawning child processes. See issue #584',
137+
);
130138

131139
// To avoid empty strings from hiding the output of commands that don't have a name,
132140
// keep in the list of commands to hide only strings with some length.

0 commit comments

Comments
 (0)