concurrently 9.2.0 → 10.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -15
- package/dist/bin/{concurrently.js → index.js} +23 -52
- package/dist/bin/index.spec.d.ts +1 -0
- package/dist/bin/index.spec.js +368 -0
- package/dist/bin/normalize-cli-command.d.ts +1 -0
- package/dist/bin/normalize-cli-command.js +15 -0
- package/dist/bin/normalize-cli-command.spec.d.ts +1 -0
- package/dist/bin/normalize-cli-command.spec.js +36 -0
- package/dist/bin/read-package-json.d.ts +4 -0
- package/dist/bin/read-package-json.js +17 -0
- package/dist/lib/__fixtures__/create-mock-instance.d.ts +2 -0
- package/dist/lib/__fixtures__/create-mock-instance.js +5 -0
- package/dist/lib/__fixtures__/fake-command.d.ts +6 -0
- package/dist/lib/__fixtures__/fake-command.js +37 -0
- package/dist/lib/assert.d.ts +10 -0
- package/dist/lib/assert.js +24 -0
- package/dist/lib/assert.spec.d.ts +1 -0
- package/dist/lib/assert.spec.js +41 -0
- package/dist/{src → lib}/command-parser/expand-arguments.d.ts +7 -7
- package/dist/lib/command-parser/expand-arguments.js +36 -0
- package/dist/lib/command-parser/expand-arguments.spec.d.ts +1 -0
- package/dist/lib/command-parser/expand-arguments.spec.js +57 -0
- package/dist/{src → lib}/command-parser/expand-shortcut.d.ts +2 -2
- package/dist/{src → lib}/command-parser/expand-shortcut.js +1 -5
- package/dist/lib/command-parser/expand-shortcut.spec.d.ts +1 -0
- package/dist/lib/command-parser/expand-shortcut.spec.js +36 -0
- package/dist/{src → lib}/command-parser/expand-wildcard.d.ts +2 -2
- package/dist/{src → lib}/command-parser/expand-wildcard.js +25 -31
- package/dist/lib/command-parser/expand-wildcard.spec.d.ts +1 -0
- package/dist/lib/command-parser/expand-wildcard.spec.js +288 -0
- package/dist/{src → lib}/command.d.ts +7 -10
- package/dist/{src → lib}/command.js +13 -32
- package/dist/lib/command.spec.d.ts +1 -0
- package/dist/lib/command.spec.js +369 -0
- package/dist/{src → lib}/completion-listener.d.ts +2 -3
- package/dist/{src → lib}/completion-listener.js +9 -36
- package/dist/lib/completion-listener.spec.d.ts +1 -0
- package/dist/lib/completion-listener.spec.js +229 -0
- package/dist/{src → lib}/concurrently.d.ts +10 -12
- package/dist/{src → lib}/concurrently.js +34 -47
- package/dist/lib/concurrently.spec.d.ts +1 -0
- package/dist/lib/concurrently.spec.js +320 -0
- package/dist/{src → lib}/date-format.d.ts +2 -2
- package/dist/{src → lib}/date-format.js +101 -77
- package/dist/lib/date-format.spec.d.ts +1 -0
- package/dist/lib/date-format.spec.js +480 -0
- package/dist/{src → lib}/defaults.d.ts +2 -6
- package/dist/{src → lib}/defaults.js +16 -23
- package/dist/{src → lib}/flow-control/input-handler.d.ts +4 -5
- package/dist/{src → lib}/flow-control/input-handler.js +8 -34
- package/dist/lib/flow-control/input-handler.spec.d.ts +1 -0
- package/dist/lib/flow-control/input-handler.spec.js +116 -0
- package/dist/{src → lib}/flow-control/kill-on-signal.d.ts +3 -5
- package/dist/{src → lib}/flow-control/kill-on-signal.js +3 -7
- package/dist/lib/flow-control/kill-on-signal.spec.d.ts +1 -0
- package/dist/lib/flow-control/kill-on-signal.spec.js +63 -0
- package/dist/{src → lib}/flow-control/kill-others.d.ts +3 -4
- package/dist/{src → lib}/flow-control/kill-others.js +8 -15
- package/dist/lib/flow-control/kill-others.spec.d.ts +1 -0
- package/dist/lib/flow-control/kill-others.spec.js +98 -0
- package/dist/{src → lib}/flow-control/log-error.d.ts +3 -3
- package/dist/{src → lib}/flow-control/log-error.js +1 -5
- package/dist/lib/flow-control/log-error.spec.d.ts +1 -0
- package/dist/lib/flow-control/log-error.spec.js +33 -0
- package/dist/{src → lib}/flow-control/log-exit.d.ts +3 -3
- package/dist/{src → lib}/flow-control/log-exit.js +1 -5
- package/dist/lib/flow-control/log-exit.spec.d.ts +1 -0
- package/dist/lib/flow-control/log-exit.spec.js +24 -0
- package/dist/{src → lib}/flow-control/log-output.d.ts +3 -3
- package/dist/{src → lib}/flow-control/log-output.js +1 -5
- package/dist/lib/flow-control/log-output.spec.d.ts +1 -0
- package/dist/lib/flow-control/log-output.spec.js +33 -0
- package/dist/{src → lib}/flow-control/log-timings.d.ts +3 -3
- package/dist/{src → lib}/flow-control/log-timings.js +11 -44
- package/dist/lib/flow-control/log-timings.spec.d.ts +1 -0
- package/dist/lib/flow-control/log-timings.spec.js +89 -0
- package/dist/{src → lib}/flow-control/logger-padding.d.ts +3 -3
- package/dist/{src → lib}/flow-control/logger-padding.js +7 -7
- package/dist/lib/flow-control/logger-padding.spec.d.ts +1 -0
- package/dist/lib/flow-control/logger-padding.spec.js +60 -0
- package/dist/{src → lib}/flow-control/output-error-handler.d.ts +4 -6
- package/dist/{src → lib}/flow-control/output-error-handler.js +3 -7
- package/dist/lib/flow-control/output-error-handler.spec.d.ts +1 -0
- package/dist/lib/flow-control/output-error-handler.spec.js +41 -0
- package/dist/{src → lib}/flow-control/restart-process.d.ts +4 -4
- package/dist/{src → lib}/flow-control/restart-process.js +10 -37
- package/dist/lib/flow-control/restart-process.spec.d.ts +1 -0
- package/dist/lib/flow-control/restart-process.spec.js +127 -0
- package/dist/{src → lib}/flow-control/teardown.d.ts +4 -5
- package/dist/{src → lib}/flow-control/teardown.js +6 -33
- package/dist/lib/flow-control/teardown.spec.d.ts +1 -0
- package/dist/lib/flow-control/teardown.spec.js +93 -0
- package/dist/{src → lib}/index.d.ts +21 -20
- package/dist/lib/index.js +98 -0
- package/dist/lib/jsonc.d.ts +8 -0
- package/dist/{src → lib}/jsonc.js +3 -8
- package/dist/lib/jsonc.spec.d.ts +1 -0
- package/dist/lib/jsonc.spec.js +73 -0
- package/dist/{src → lib}/logger.d.ts +3 -2
- package/dist/{src → lib}/logger.js +112 -53
- package/dist/lib/logger.spec.d.ts +1 -0
- package/dist/lib/logger.spec.js +507 -0
- package/dist/{src → lib}/observables.d.ts +1 -2
- package/dist/{src → lib}/observables.js +3 -7
- package/dist/lib/observables.spec.d.ts +1 -0
- package/dist/lib/observables.spec.js +29 -0
- package/dist/{src → lib}/output-writer.d.ts +3 -4
- package/dist/{src → lib}/output-writer.js +4 -31
- package/dist/lib/output-writer.spec.d.ts +1 -0
- package/dist/lib/output-writer.spec.js +96 -0
- package/dist/lib/prefix-color-selector.d.ts +21 -0
- package/dist/{src → lib}/prefix-color-selector.js +14 -31
- package/dist/lib/prefix-color-selector.spec.d.ts +1 -0
- package/dist/lib/prefix-color-selector.spec.js +159 -0
- package/dist/{src → lib}/spawn.d.ts +19 -16
- package/dist/lib/spawn.js +105 -0
- package/dist/lib/spawn.spec.d.ts +1 -0
- package/dist/lib/spawn.spec.js +100 -0
- package/dist/lib/utils.d.ts +25 -0
- package/dist/lib/utils.js +52 -0
- package/dist/lib/utils.spec.d.ts +1 -0
- package/dist/lib/utils.spec.js +58 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/docs/README.md +6 -0
- package/docs/cli/passthrough-arguments.md +8 -8
- package/docs/cli/prefixing.md +44 -4
- package/docs/cli/shortcuts.md +2 -2
- package/docs/shell-resolution.md +48 -0
- package/package.json +64 -85
- package/dist/bin/read-package.d.ts +0 -6
- package/dist/bin/read-package.js +0 -47
- package/dist/src/assert.d.ts +0 -5
- package/dist/src/assert.js +0 -16
- package/dist/src/command-parser/command-parser.d.ts +0 -19
- package/dist/src/command-parser/command-parser.js +0 -2
- package/dist/src/command-parser/expand-arguments.js +0 -39
- package/dist/src/command-parser/strip-quotes.d.ts +0 -16
- package/dist/src/command-parser/strip-quotes.js +0 -17
- package/dist/src/flow-control/flow-controller.d.ts +0 -13
- package/dist/src/flow-control/flow-controller.js +0 -2
- package/dist/src/index.js +0 -99
- package/dist/src/jsonc.d.ts +0 -8
- package/dist/src/prefix-color-selector.d.ts +0 -11
- package/dist/src/spawn.js +0 -49
- package/index.d.mts +0 -7
- package/index.d.ts +0 -11
- package/index.js +0 -14
- package/index.mjs +0 -10
- /package/dist/bin/{concurrently.d.ts → index.d.ts} +0 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { Writable } from 'node:stream';
|
|
2
|
+
import { beforeEach, describe, expect, it } from 'vitest';
|
|
3
|
+
import { createMockInstance } from './__fixtures__/create-mock-instance.js';
|
|
4
|
+
import { createFakeCloseEvent, FakeCommand } from './__fixtures__/fake-command.js';
|
|
5
|
+
import { OutputWriter } from './output-writer.js';
|
|
6
|
+
let outputStream;
|
|
7
|
+
let commands;
|
|
8
|
+
function createWriter(overrides) {
|
|
9
|
+
const options = {
|
|
10
|
+
outputStream,
|
|
11
|
+
group: false,
|
|
12
|
+
commands,
|
|
13
|
+
...overrides,
|
|
14
|
+
};
|
|
15
|
+
return new OutputWriter(options);
|
|
16
|
+
}
|
|
17
|
+
function closeCommand(command) {
|
|
18
|
+
command.state = 'exited';
|
|
19
|
+
command.close.next(createFakeCloseEvent({ command, index: command.index }));
|
|
20
|
+
}
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
outputStream = createMockInstance(Writable);
|
|
23
|
+
commands = [
|
|
24
|
+
new FakeCommand('', undefined, 0),
|
|
25
|
+
new FakeCommand('', undefined, 1),
|
|
26
|
+
new FakeCommand('', undefined, 2),
|
|
27
|
+
];
|
|
28
|
+
});
|
|
29
|
+
it('throws if outputStream already is in errored state', () => {
|
|
30
|
+
Object.defineProperty(outputStream, 'errored', { value: new Error('test') });
|
|
31
|
+
expect(() => createWriter()).toThrow(TypeError);
|
|
32
|
+
});
|
|
33
|
+
describe('#write()', () => {
|
|
34
|
+
it('throws if outputStream has errored', () => {
|
|
35
|
+
const writer = createWriter();
|
|
36
|
+
Object.defineProperty(outputStream, 'errored', { value: new Error('test') });
|
|
37
|
+
expect(() => writer.write(commands[0], 'hello')).toThrow(TypeError);
|
|
38
|
+
});
|
|
39
|
+
describe('with group=false', () => {
|
|
40
|
+
it('writes instantly', () => {
|
|
41
|
+
const writer = createWriter({ group: false });
|
|
42
|
+
writer.write(commands[2], 'hello');
|
|
43
|
+
expect(outputStream.write).toHaveBeenCalledExactlyOnceWith('hello');
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
describe('with group=true', () => {
|
|
47
|
+
it('writes for null commands', () => {
|
|
48
|
+
const writer = createWriter({ group: true });
|
|
49
|
+
writer.write(undefined, 'hello');
|
|
50
|
+
expect(outputStream.write).toHaveBeenCalledExactlyOnceWith('hello');
|
|
51
|
+
});
|
|
52
|
+
it('does not write instantly for non-active command', () => {
|
|
53
|
+
const writer = createWriter({ group: true });
|
|
54
|
+
writer.write(commands[2], 'hello');
|
|
55
|
+
expect(outputStream.write).toHaveBeenCalledTimes(0);
|
|
56
|
+
expect(writer.buffers[2]).toEqual(['hello']);
|
|
57
|
+
});
|
|
58
|
+
it('write instantly for active command', () => {
|
|
59
|
+
const writer = createWriter({ group: true });
|
|
60
|
+
writer.write(commands[0], 'hello');
|
|
61
|
+
expect(outputStream.write).toHaveBeenCalledExactlyOnceWith('hello');
|
|
62
|
+
});
|
|
63
|
+
it('does not wait for write from next command to flush', () => {
|
|
64
|
+
const writer = createWriter({ group: true });
|
|
65
|
+
writer.write(commands[1], 'hello');
|
|
66
|
+
writer.write(commands[1], 'foo bar');
|
|
67
|
+
expect(outputStream.write).toHaveBeenCalledTimes(0);
|
|
68
|
+
closeCommand(commands[0]);
|
|
69
|
+
expect(outputStream.write).toHaveBeenCalledTimes(2);
|
|
70
|
+
expect(writer.activeCommandIndex).toBe(1);
|
|
71
|
+
outputStream.write.mockClear();
|
|
72
|
+
writer.write(commands[1], 'blah');
|
|
73
|
+
expect(outputStream.write).toHaveBeenCalledTimes(1);
|
|
74
|
+
});
|
|
75
|
+
it('does not flush for non-active command', () => {
|
|
76
|
+
const writer = createWriter({ group: true });
|
|
77
|
+
writer.write(commands[1], 'hello');
|
|
78
|
+
writer.write(commands[1], 'foo bar');
|
|
79
|
+
expect(outputStream.write).toHaveBeenCalledTimes(0);
|
|
80
|
+
closeCommand(commands[1]);
|
|
81
|
+
expect(outputStream.write).toHaveBeenCalledTimes(0);
|
|
82
|
+
closeCommand(commands[0]);
|
|
83
|
+
expect(outputStream.write).toHaveBeenCalledTimes(2);
|
|
84
|
+
});
|
|
85
|
+
it('flushes multiple commands at a time if necessary', () => {
|
|
86
|
+
const writer = createWriter({ group: true });
|
|
87
|
+
writer.write(commands[2], 'hello');
|
|
88
|
+
closeCommand(commands[1]);
|
|
89
|
+
closeCommand(commands[2]);
|
|
90
|
+
expect(outputStream.write).toHaveBeenCalledTimes(0);
|
|
91
|
+
closeCommand(commands[0]);
|
|
92
|
+
expect(outputStream.write).toHaveBeenCalledExactlyOnceWith('hello');
|
|
93
|
+
expect(writer.activeCommandIndex).toBe(2);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ChalkInstance } from 'chalk';
|
|
2
|
+
export declare class PrefixColorSelector {
|
|
3
|
+
private colorGenerator;
|
|
4
|
+
constructor(customColors?: string | string[]);
|
|
5
|
+
/**
|
|
6
|
+
* Colors used by `auto` selection and default cycling.
|
|
7
|
+
*
|
|
8
|
+
* Each color is chosen to be visually distinct on both dark and light
|
|
9
|
+
* terminal backgrounds, without carrying semantic meaning (e.g. red
|
|
10
|
+
* implies errors) or blending into default text (e.g. white/grey).
|
|
11
|
+
* Background colors are excluded to keep output lightweight.
|
|
12
|
+
*
|
|
13
|
+
* This list does NOT restrict manually specified colors — any valid Chalk
|
|
14
|
+
* color name, hex value, or modifier can be passed via `--prefix-colors`.
|
|
15
|
+
*/
|
|
16
|
+
static get ACCEPTABLE_CONSOLE_COLORS(): (keyof ChalkInstance)[];
|
|
17
|
+
/**
|
|
18
|
+
* @returns The given custom colors then a set of acceptable console colors indefinitely.
|
|
19
|
+
*/
|
|
20
|
+
getNextColor(): string;
|
|
21
|
+
}
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.PrefixColorSelector = void 0;
|
|
4
1
|
function getConsoleColorsWithoutCustomColors(customColors) {
|
|
5
2
|
return PrefixColorSelector.ACCEPTABLE_CONSOLE_COLORS.filter(
|
|
6
3
|
// Consider the "Bright" variants of colors to be the same as the plain color to avoid similar colors
|
|
@@ -51,44 +48,30 @@ function* createColorGenerator(customColors) {
|
|
|
51
48
|
}
|
|
52
49
|
}
|
|
53
50
|
}
|
|
54
|
-
class PrefixColorSelector {
|
|
51
|
+
export class PrefixColorSelector {
|
|
55
52
|
colorGenerator;
|
|
56
53
|
constructor(customColors = []) {
|
|
57
54
|
const normalizedColors = typeof customColors === 'string' ? [customColors] : customColors;
|
|
58
55
|
this.colorGenerator = createColorGenerator(normalizedColors);
|
|
59
56
|
}
|
|
60
|
-
/**
|
|
57
|
+
/**
|
|
58
|
+
* Colors used by `auto` selection and default cycling.
|
|
59
|
+
*
|
|
60
|
+
* Each color is chosen to be visually distinct on both dark and light
|
|
61
|
+
* terminal backgrounds, without carrying semantic meaning (e.g. red
|
|
62
|
+
* implies errors) or blending into default text (e.g. white/grey).
|
|
63
|
+
* Background colors are excluded to keep output lightweight.
|
|
64
|
+
*
|
|
65
|
+
* This list does NOT restrict manually specified colors — any valid Chalk
|
|
66
|
+
* color name, hex value, or modifier can be passed via `--prefix-colors`.
|
|
67
|
+
*/
|
|
61
68
|
static get ACCEPTABLE_CONSOLE_COLORS() {
|
|
62
|
-
|
|
63
|
-
return [
|
|
64
|
-
// Prevent duplicates, in case the list becomes significantly large
|
|
65
|
-
...new Set([
|
|
66
|
-
// Text colors
|
|
67
|
-
'cyan',
|
|
68
|
-
'yellow',
|
|
69
|
-
'greenBright',
|
|
70
|
-
'blueBright',
|
|
71
|
-
'magentaBright',
|
|
72
|
-
'white',
|
|
73
|
-
'grey',
|
|
74
|
-
'red',
|
|
75
|
-
// Background colors
|
|
76
|
-
'bgCyan',
|
|
77
|
-
'bgYellow',
|
|
78
|
-
'bgGreenBright',
|
|
79
|
-
'bgBlueBright',
|
|
80
|
-
'bgMagenta',
|
|
81
|
-
'bgWhiteBright',
|
|
82
|
-
'bgGrey',
|
|
83
|
-
'bgRed',
|
|
84
|
-
]),
|
|
85
|
-
];
|
|
69
|
+
return [...new Set(['cyan', 'magenta', 'green', 'yellow', 'blue'])];
|
|
86
70
|
}
|
|
87
71
|
/**
|
|
88
72
|
* @returns The given custom colors then a set of acceptable console colors indefinitely.
|
|
89
73
|
*/
|
|
90
74
|
getNextColor() {
|
|
91
|
-
return this.colorGenerator.next().value;
|
|
75
|
+
return this.colorGenerator.next().value.trim();
|
|
92
76
|
}
|
|
93
77
|
}
|
|
94
|
-
exports.PrefixColorSelector = PrefixColorSelector;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { PrefixColorSelector } from './prefix-color-selector.js';
|
|
3
|
+
afterEach(() => {
|
|
4
|
+
vi.restoreAllMocks();
|
|
5
|
+
});
|
|
6
|
+
describe('#getNextColor()', () => {
|
|
7
|
+
const customTests = {
|
|
8
|
+
'does not produce a color if prefixColors empty': {
|
|
9
|
+
customColors: [],
|
|
10
|
+
expectedColors: ['', '', ''],
|
|
11
|
+
},
|
|
12
|
+
'does not produce a color if prefixColors undefined': {
|
|
13
|
+
expectedColors: ['', '', ''],
|
|
14
|
+
},
|
|
15
|
+
'uses user defined prefix colors only, if no auto is used': {
|
|
16
|
+
customColors: ['red', 'green', 'blue'],
|
|
17
|
+
expectedColors: [
|
|
18
|
+
'red',
|
|
19
|
+
'green',
|
|
20
|
+
'blue',
|
|
21
|
+
// Uses last color if last color is not "auto"
|
|
22
|
+
'blue',
|
|
23
|
+
'blue',
|
|
24
|
+
'blue',
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
'trims colors': {
|
|
28
|
+
customColors: [' red ', ' green ', ' blue '],
|
|
29
|
+
expectedColors: ['red', 'green', 'blue'],
|
|
30
|
+
},
|
|
31
|
+
'accepts a string value for customColors': {
|
|
32
|
+
customColors: 'red',
|
|
33
|
+
expectedColors: ['red', 'red'],
|
|
34
|
+
},
|
|
35
|
+
'picks varying colors when user defines an auto color': {
|
|
36
|
+
acceptableConsoleColors: ['green', 'blue'],
|
|
37
|
+
customColors: [
|
|
38
|
+
'red',
|
|
39
|
+
'green',
|
|
40
|
+
'auto',
|
|
41
|
+
'green',
|
|
42
|
+
'auto',
|
|
43
|
+
'green',
|
|
44
|
+
'auto',
|
|
45
|
+
'blue',
|
|
46
|
+
'auto',
|
|
47
|
+
'orange',
|
|
48
|
+
],
|
|
49
|
+
expectedColors: [
|
|
50
|
+
// Custom colors
|
|
51
|
+
'red',
|
|
52
|
+
'green',
|
|
53
|
+
'blue', // Picks auto color "blue", not repeating consecutive "green" color
|
|
54
|
+
'green', // Manual
|
|
55
|
+
'blue', // Auto picks "blue" not to repeat last
|
|
56
|
+
'green', // Manual
|
|
57
|
+
'blue', // Auto picks "blue" again not to repeat last
|
|
58
|
+
'blue', // Manual
|
|
59
|
+
'green', // Auto picks "green" again not to repeat last
|
|
60
|
+
'orange',
|
|
61
|
+
// Uses last color if last color is not "auto"
|
|
62
|
+
'orange',
|
|
63
|
+
'orange',
|
|
64
|
+
'orange',
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
'uses user defined colors then recurring auto colors without repeating consecutive colors': {
|
|
68
|
+
acceptableConsoleColors: ['green', 'blue'],
|
|
69
|
+
customColors: ['red', 'green', 'auto'],
|
|
70
|
+
expectedColors: [
|
|
71
|
+
// Custom colors
|
|
72
|
+
'red',
|
|
73
|
+
'green',
|
|
74
|
+
// Picks auto colors, not repeating consecutive "green" color
|
|
75
|
+
'blue',
|
|
76
|
+
'green',
|
|
77
|
+
'blue',
|
|
78
|
+
'green',
|
|
79
|
+
],
|
|
80
|
+
},
|
|
81
|
+
'can sometimes produce consecutive colors': {
|
|
82
|
+
acceptableConsoleColors: ['green', 'blue'],
|
|
83
|
+
customColors: ['blue', 'auto'],
|
|
84
|
+
expectedColors: [
|
|
85
|
+
// Custom colors
|
|
86
|
+
'blue',
|
|
87
|
+
// Picks auto colors
|
|
88
|
+
'green',
|
|
89
|
+
// Does not repeat custom colors for initial auto colors, i.e. does not use "blue" again so soon
|
|
90
|
+
'green', // Consecutive color picked, however practically there would be a lot of colors that need to be set in a particular order for this to occur
|
|
91
|
+
'blue',
|
|
92
|
+
'green',
|
|
93
|
+
'blue',
|
|
94
|
+
'green',
|
|
95
|
+
'blue',
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
'considers the Bright variants of colors equal to the normal colors to avoid similar colors': {
|
|
99
|
+
acceptableConsoleColors: ['greenBright', 'blueBright', 'green', 'blue', 'magenta'],
|
|
100
|
+
customColors: ['green', 'blue', 'auto'],
|
|
101
|
+
expectedColors: [
|
|
102
|
+
// Custom colors
|
|
103
|
+
'green',
|
|
104
|
+
'blue',
|
|
105
|
+
// Picks auto colors, not repeating green and blue colors and variants initially
|
|
106
|
+
'magenta',
|
|
107
|
+
// Picks auto colors
|
|
108
|
+
'greenBright',
|
|
109
|
+
'blueBright',
|
|
110
|
+
'green',
|
|
111
|
+
'blue',
|
|
112
|
+
'magenta',
|
|
113
|
+
],
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
it.each(Object.entries(customTests))('%s', (_, { acceptableConsoleColors, customColors, expectedColors }) => {
|
|
117
|
+
if (acceptableConsoleColors) {
|
|
118
|
+
vi.spyOn(PrefixColorSelector, 'ACCEPTABLE_CONSOLE_COLORS', 'get').mockReturnValue(acceptableConsoleColors);
|
|
119
|
+
}
|
|
120
|
+
const prefixColorSelector = new PrefixColorSelector(customColors);
|
|
121
|
+
const prefixColorSelectorValues = expectedColors.map(() => prefixColorSelector.getNextColor());
|
|
122
|
+
expect(prefixColorSelectorValues).toEqual(expectedColors);
|
|
123
|
+
});
|
|
124
|
+
const autoTests = {
|
|
125
|
+
'does not repeat consecutive colors when last prefixColor is auto': false,
|
|
126
|
+
'handles when more individual auto prefixColors exist than acceptable console colors': true,
|
|
127
|
+
};
|
|
128
|
+
it.each(Object.entries(autoTests))('%s', (_, map) => {
|
|
129
|
+
// Pick auto colors over 2 sets
|
|
130
|
+
const expectedColors = [
|
|
131
|
+
...PrefixColorSelector.ACCEPTABLE_CONSOLE_COLORS,
|
|
132
|
+
...PrefixColorSelector.ACCEPTABLE_CONSOLE_COLORS,
|
|
133
|
+
];
|
|
134
|
+
const prefixColorSelector = new PrefixColorSelector(map ? expectedColors.map(() => 'auto') : ['auto']);
|
|
135
|
+
let previousColor = '';
|
|
136
|
+
for (const expectedColor of expectedColors) {
|
|
137
|
+
const actualSelectedColor = prefixColorSelector.getNextColor();
|
|
138
|
+
expect(actualSelectedColor).not.toBe(previousColor); // No consecutive colors
|
|
139
|
+
expect(actualSelectedColor).toBe(expectedColor); // Expected color
|
|
140
|
+
previousColor = actualSelectedColor;
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
describe('#ACCEPTABLE_CONSOLE_COLORS', () => {
|
|
145
|
+
it('has more than 1 auto color defined', () => {
|
|
146
|
+
// (!) The current implementation is based on the assumption that 'ACCEPTABLE_CONSOLE_COLORS'
|
|
147
|
+
// always has more than one entry, which is what we enforce via this test
|
|
148
|
+
expect(PrefixColorSelector.ACCEPTABLE_CONSOLE_COLORS.length).toBeGreaterThan(1);
|
|
149
|
+
});
|
|
150
|
+
it('only includes colors that are visually distinct, semantically neutral, and lightweight', () => {
|
|
151
|
+
expect(PrefixColorSelector.ACCEPTABLE_CONSOLE_COLORS).toEqual([
|
|
152
|
+
'cyan',
|
|
153
|
+
'magenta',
|
|
154
|
+
'green',
|
|
155
|
+
'yellow',
|
|
156
|
+
'blue',
|
|
157
|
+
]);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
@@ -1,15 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
/// <reference types="node" />
|
|
5
|
-
/// <reference types="node" />
|
|
6
|
-
/// <reference types="node" />
|
|
7
|
-
import { ChildProcess, SpawnOptions } from 'child_process';
|
|
8
|
-
import supportsColor from 'supports-color';
|
|
1
|
+
import { ChildProcess, SpawnOptions } from 'node:child_process';
|
|
2
|
+
import { ColorSupport } from 'supports-color';
|
|
3
|
+
import { SpawnCommand } from './command.js';
|
|
9
4
|
/**
|
|
10
|
-
*
|
|
5
|
+
* Creates a spawn function that uses the given shell executable.
|
|
6
|
+
*
|
|
7
|
+
* The shell is resolved in the following priority order:
|
|
8
|
+
* 1. explicit shell option
|
|
9
|
+
* 2. `npm_config_script_shell` env variable
|
|
10
|
+
* 3. platform default (`cmd.exe` on Windows, `/bin/sh` elsewhere)
|
|
11
|
+
*
|
|
12
|
+
* @see https://docs.npmjs.com/cli/v6/using-npm/config#script-shell
|
|
11
13
|
*/
|
|
12
|
-
export declare function
|
|
14
|
+
export declare function createSpawn(shell?: string, spawn?: (command: string, args: string[], options: SpawnOptions) => ChildProcess, process?: Pick<NodeJS.Process, 'platform' | 'env'>): SpawnCommand;
|
|
15
|
+
export type ShellKind = 'cmd' | 'posix' | 'powershell';
|
|
13
16
|
export declare const getSpawnOpts: ({ colorSupport, cwd, process, ipc, stdio, env, }: {
|
|
14
17
|
/**
|
|
15
18
|
* What the color support of the spawned processes should be.
|
|
@@ -17,21 +20,21 @@ export declare const getSpawnOpts: ({ colorSupport, cwd, process, ipc, stdio, en
|
|
|
17
20
|
*
|
|
18
21
|
* Defaults to whatever the terminal's stdout support is.
|
|
19
22
|
*/
|
|
20
|
-
colorSupport?:
|
|
23
|
+
colorSupport?: Pick<ColorSupport, "level"> | false;
|
|
21
24
|
/**
|
|
22
25
|
* The NodeJS process.
|
|
23
26
|
*/
|
|
24
|
-
process?: Pick<NodeJS.Process, "
|
|
27
|
+
process?: Pick<NodeJS.Process, "cwd" | "platform" | "env">;
|
|
25
28
|
/**
|
|
26
29
|
* A custom working directory to spawn processes in.
|
|
27
30
|
* Defaults to `process.cwd()`.
|
|
28
31
|
*/
|
|
29
|
-
cwd?: string
|
|
32
|
+
cwd?: string;
|
|
30
33
|
/**
|
|
31
34
|
* The file descriptor number at which the channel for inter-process communication
|
|
32
35
|
* should be set up.
|
|
33
36
|
*/
|
|
34
|
-
ipc?: number
|
|
37
|
+
ipc?: number;
|
|
35
38
|
/**
|
|
36
39
|
* Which stdio mode to use. Raw implies inheriting the parent process' stdio.
|
|
37
40
|
*
|
|
@@ -41,9 +44,9 @@ export declare const getSpawnOpts: ({ colorSupport, cwd, process, ipc, stdio, en
|
|
|
41
44
|
*
|
|
42
45
|
* Defaults to `normal`.
|
|
43
46
|
*/
|
|
44
|
-
stdio?: "
|
|
47
|
+
stdio?: "normal" | "hidden" | "raw";
|
|
45
48
|
/**
|
|
46
49
|
* Map of custom environment variables to include in the spawn options.
|
|
47
50
|
*/
|
|
48
|
-
env?: Record<string, unknown
|
|
51
|
+
env?: Record<string, unknown>;
|
|
49
52
|
}) => SpawnOptions;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import assert from 'node:assert';
|
|
2
|
+
import { spawn as baseSpawn } from 'node:child_process';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import nodeProcess from 'node:process';
|
|
5
|
+
import supportsColor from 'supports-color';
|
|
6
|
+
import { UnreachableError } from './utils.js';
|
|
7
|
+
/**
|
|
8
|
+
* Creates a spawn function that uses the given shell executable.
|
|
9
|
+
*
|
|
10
|
+
* The shell is resolved in the following priority order:
|
|
11
|
+
* 1. explicit shell option
|
|
12
|
+
* 2. `npm_config_script_shell` env variable
|
|
13
|
+
* 3. platform default (`cmd.exe` on Windows, `/bin/sh` elsewhere)
|
|
14
|
+
*
|
|
15
|
+
* @see https://docs.npmjs.com/cli/v6/using-npm/config#script-shell
|
|
16
|
+
*/
|
|
17
|
+
export function createSpawn(shell,
|
|
18
|
+
// For testing
|
|
19
|
+
spawn = baseSpawn, process = nodeProcess) {
|
|
20
|
+
const resolved = resolveShell(shell, process);
|
|
21
|
+
return (command, spawnOpts) => {
|
|
22
|
+
const { file, args, shellOptions } = getShellSpawnArgs(resolved, command);
|
|
23
|
+
return spawn(file, args, { ...spawnOpts, ...shellOptions });
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
const NPM_SCRIPT_SHELL_ENV = 'npm_config_script_shell';
|
|
27
|
+
/**
|
|
28
|
+
* Resolves which shell executable to use when spawning commands.
|
|
29
|
+
* @see {@link createSpawn()}
|
|
30
|
+
*/
|
|
31
|
+
function resolveShell(shell, process = nodeProcess) {
|
|
32
|
+
if (shell) {
|
|
33
|
+
return shell;
|
|
34
|
+
}
|
|
35
|
+
const npmScriptShell = process.env[NPM_SCRIPT_SHELL_ENV];
|
|
36
|
+
if (npmScriptShell) {
|
|
37
|
+
return npmScriptShell;
|
|
38
|
+
}
|
|
39
|
+
return process.platform === 'win32' ? 'cmd.exe' : '/bin/sh';
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Builds spawn file/args for the given shell and command string.
|
|
43
|
+
*/
|
|
44
|
+
function getShellSpawnArgs(shellPath, command) {
|
|
45
|
+
const kind = detectShellKind(shellPath);
|
|
46
|
+
switch (kind) {
|
|
47
|
+
case 'cmd':
|
|
48
|
+
return {
|
|
49
|
+
file: shellPath,
|
|
50
|
+
args: ['/s', '/c', `"${command}"`],
|
|
51
|
+
shellOptions: { windowsVerbatimArguments: true },
|
|
52
|
+
};
|
|
53
|
+
case 'powershell':
|
|
54
|
+
return {
|
|
55
|
+
file: shellPath,
|
|
56
|
+
args: ['-NoProfile', '-Command', command],
|
|
57
|
+
};
|
|
58
|
+
case 'posix':
|
|
59
|
+
return {
|
|
60
|
+
file: shellPath,
|
|
61
|
+
args: ['-c', command],
|
|
62
|
+
};
|
|
63
|
+
default:
|
|
64
|
+
throw new UnreachableError(kind);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Detects which argument style to use when spawning the given shell executable.
|
|
69
|
+
*/
|
|
70
|
+
function detectShellKind(shellPath) {
|
|
71
|
+
const normalized = shellPath.replace(/\\/g, '/');
|
|
72
|
+
const base = path
|
|
73
|
+
.basename(normalized)
|
|
74
|
+
.toLowerCase()
|
|
75
|
+
.replace(/\.exe$/i, '');
|
|
76
|
+
if (base === 'cmd') {
|
|
77
|
+
return 'cmd';
|
|
78
|
+
}
|
|
79
|
+
if (base === 'powershell' || base === 'pwsh') {
|
|
80
|
+
return 'powershell';
|
|
81
|
+
}
|
|
82
|
+
return 'posix';
|
|
83
|
+
}
|
|
84
|
+
export const getSpawnOpts = ({ colorSupport = supportsColor.stdout, cwd, process = nodeProcess, ipc, stdio = 'normal', env = {}, }) => {
|
|
85
|
+
const stdioValues = stdio === 'normal'
|
|
86
|
+
? ['pipe', 'pipe', 'pipe']
|
|
87
|
+
: stdio === 'raw'
|
|
88
|
+
? ['inherit', 'inherit', 'inherit']
|
|
89
|
+
: ['pipe', 'ignore', 'ignore'];
|
|
90
|
+
if (ipc != null) {
|
|
91
|
+
// Avoid overriding the stdout/stderr/stdin
|
|
92
|
+
assert.ok(ipc > 2, '[concurrently] the IPC channel number should be > 2');
|
|
93
|
+
stdioValues[ipc] = 'ipc';
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
cwd: cwd || process.cwd(),
|
|
97
|
+
stdio: stdioValues,
|
|
98
|
+
...(process.platform.startsWith('win') && { detached: false }),
|
|
99
|
+
env: {
|
|
100
|
+
...(colorSupport ? { FORCE_COLOR: colorSupport.level.toString() } : {}),
|
|
101
|
+
...process.env,
|
|
102
|
+
...env,
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { createSpawn, getSpawnOpts } from './spawn.js';
|
|
3
|
+
import { UnreachableError } from './utils.js';
|
|
4
|
+
const baseProcess = {
|
|
5
|
+
platform: 'win32',
|
|
6
|
+
cwd: () => '',
|
|
7
|
+
env: {},
|
|
8
|
+
};
|
|
9
|
+
describe('createSpawn()', () => {
|
|
10
|
+
const command = 'echo banana';
|
|
11
|
+
const makeShellArgs = (kind) => {
|
|
12
|
+
switch (kind) {
|
|
13
|
+
case 'cmd':
|
|
14
|
+
return ['/s', '/c', `"${command}"`];
|
|
15
|
+
case 'posix':
|
|
16
|
+
return ['-c', command];
|
|
17
|
+
case 'powershell':
|
|
18
|
+
return ['-NoProfile', '-Command', command];
|
|
19
|
+
default:
|
|
20
|
+
throw new UnreachableError(kind);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
describe('when shell is not provided', () => {
|
|
24
|
+
it('uses npm_config_script_shell when set', () => {
|
|
25
|
+
const fakeSpawn = vi.fn();
|
|
26
|
+
const spawn = createSpawn(undefined, fakeSpawn, {
|
|
27
|
+
...baseProcess,
|
|
28
|
+
env: { npm_config_script_shell: 'C:\\Git\\bin\\bash.exe' },
|
|
29
|
+
});
|
|
30
|
+
spawn(command, {});
|
|
31
|
+
expect(fakeSpawn).toHaveBeenCalledWith('C:\\Git\\bin\\bash.exe', ['-c', command], {});
|
|
32
|
+
});
|
|
33
|
+
it('creates spawn function that uses cmd.exe on Windows', () => {
|
|
34
|
+
const fakeSpawn = vi.fn();
|
|
35
|
+
const spawn = createSpawn(undefined, fakeSpawn, { ...baseProcess, platform: 'win32' });
|
|
36
|
+
spawn(command, {});
|
|
37
|
+
expect(fakeSpawn).toHaveBeenCalledWith('cmd.exe', ['/s', '/c', `"${command}"`], {
|
|
38
|
+
windowsVerbatimArguments: true,
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
it('creates spawn function that uses /bin/sh on non-Windows platforms', () => {
|
|
42
|
+
const fakeSpawn = vi.fn();
|
|
43
|
+
const spawn = createSpawn(undefined, fakeSpawn, { ...baseProcess, platform: 'linux' });
|
|
44
|
+
spawn(command, {});
|
|
45
|
+
expect(fakeSpawn).toHaveBeenCalledWith('/bin/sh', ['-c', command], expect.objectContaining({}));
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
describe.each([
|
|
49
|
+
{ style: 'cmd', file: 'cmd.exe' },
|
|
50
|
+
{ style: 'posix', file: 'C:\\bash.exe' },
|
|
51
|
+
{ style: 'powershell', file: 'pwsh' },
|
|
52
|
+
{ style: 'posix', file: '/bin/sh' },
|
|
53
|
+
{ style: 'posix', file: '/bin/zsh' },
|
|
54
|
+
])('when shell is set to $file', ({ style, file }) => {
|
|
55
|
+
it(`creates spawn function that uses ${file} as shell with ${style} style arguments`, () => {
|
|
56
|
+
const fakeSpawn = vi.fn();
|
|
57
|
+
const spawn = createSpawn(file, fakeSpawn, baseProcess);
|
|
58
|
+
spawn(command, {});
|
|
59
|
+
expect(fakeSpawn).toHaveBeenCalledWith(file, makeShellArgs(style), expect.objectContaining({}));
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
describe('getSpawnOpts()', () => {
|
|
64
|
+
it('sets detached mode to false for Windows platform', () => {
|
|
65
|
+
expect(getSpawnOpts({ process: baseProcess }).detached).toBe(false);
|
|
66
|
+
});
|
|
67
|
+
it('sets stdio to pipe when stdio mode is normal', () => {
|
|
68
|
+
expect(getSpawnOpts({ stdio: 'normal' }).stdio).toEqual(['pipe', 'pipe', 'pipe']);
|
|
69
|
+
});
|
|
70
|
+
it('sets stdio to inherit when stdio mode is raw', () => {
|
|
71
|
+
expect(getSpawnOpts({ stdio: 'raw' }).stdio).toEqual(['inherit', 'inherit', 'inherit']);
|
|
72
|
+
});
|
|
73
|
+
it('sets stdio to ignore stdout + stderr when stdio mode is hidden', () => {
|
|
74
|
+
expect(getSpawnOpts({ stdio: 'hidden' }).stdio).toEqual(['pipe', 'ignore', 'ignore']);
|
|
75
|
+
});
|
|
76
|
+
it('sets an ipc channel at the specified descriptor index', () => {
|
|
77
|
+
const opts = getSpawnOpts({ ipc: 3 });
|
|
78
|
+
expect(opts.stdio?.[3]).toBe('ipc');
|
|
79
|
+
});
|
|
80
|
+
it('throws if the ipc channel is <= 2', () => {
|
|
81
|
+
const fn = () => getSpawnOpts({ ipc: 0 });
|
|
82
|
+
expect(fn).toThrow();
|
|
83
|
+
});
|
|
84
|
+
it('merges FORCE_COLOR into env vars if color supported', () => {
|
|
85
|
+
const process = { ...baseProcess, env: { foo: 'bar' } };
|
|
86
|
+
expect(getSpawnOpts({ process, colorSupport: false }).env).toEqual(process.env);
|
|
87
|
+
expect(getSpawnOpts({ process, colorSupport: { level: 1 } }).env).toEqual({
|
|
88
|
+
FORCE_COLOR: '1',
|
|
89
|
+
foo: 'bar',
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
it('sets default cwd to process.cwd()', () => {
|
|
93
|
+
const process = { ...baseProcess, cwd: () => 'process-cwd' };
|
|
94
|
+
expect(getSpawnOpts({ process }).cwd).toBe('process-cwd');
|
|
95
|
+
});
|
|
96
|
+
it('overrides default cwd', () => {
|
|
97
|
+
const cwd = 'foobar';
|
|
98
|
+
expect(getSpawnOpts({ cwd }).cwd).toBe(cwd);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Escapes a string for use in a regular expression.
|
|
3
|
+
*/
|
|
4
|
+
export declare function escapeRegExp(str: string): string;
|
|
5
|
+
type CastArrayResult<T> = T extends undefined | null ? never[] : T extends unknown[] ? T : T[];
|
|
6
|
+
/**
|
|
7
|
+
* Casts a value to an array if it's not one.
|
|
8
|
+
*/
|
|
9
|
+
export declare function castArray<T = never[]>(value?: T): CastArrayResult<T>;
|
|
10
|
+
/**
|
|
11
|
+
* Splits a string on `delimiter`, ignoring delimiters inside parentheses.
|
|
12
|
+
* Trims each segment and discards empty ones.
|
|
13
|
+
*
|
|
14
|
+
* Examples:
|
|
15
|
+
* splitOutsideParens('red,rgb(255,0,0),blue', ',') → ['red', 'rgb(255,0,0)', 'blue']
|
|
16
|
+
* splitOutsideParens('black.bgHex(#533AFD).dim', '.') → ['black', 'bgHex(#533AFD)', 'dim']
|
|
17
|
+
*/
|
|
18
|
+
export declare function splitOutsideParens(input: string, delimiter: string): string[];
|
|
19
|
+
/**
|
|
20
|
+
* Error thrown when a condition is reached that should be impossible.
|
|
21
|
+
*/
|
|
22
|
+
export declare class UnreachableError extends Error {
|
|
23
|
+
constructor(value: never);
|
|
24
|
+
}
|
|
25
|
+
export {};
|