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.
Files changed (149) hide show
  1. package/README.md +25 -15
  2. package/dist/bin/{concurrently.js → index.js} +23 -52
  3. package/dist/bin/index.spec.d.ts +1 -0
  4. package/dist/bin/index.spec.js +368 -0
  5. package/dist/bin/normalize-cli-command.d.ts +1 -0
  6. package/dist/bin/normalize-cli-command.js +15 -0
  7. package/dist/bin/normalize-cli-command.spec.d.ts +1 -0
  8. package/dist/bin/normalize-cli-command.spec.js +36 -0
  9. package/dist/bin/read-package-json.d.ts +4 -0
  10. package/dist/bin/read-package-json.js +17 -0
  11. package/dist/lib/__fixtures__/create-mock-instance.d.ts +2 -0
  12. package/dist/lib/__fixtures__/create-mock-instance.js +5 -0
  13. package/dist/lib/__fixtures__/fake-command.d.ts +6 -0
  14. package/dist/lib/__fixtures__/fake-command.js +37 -0
  15. package/dist/lib/assert.d.ts +10 -0
  16. package/dist/lib/assert.js +24 -0
  17. package/dist/lib/assert.spec.d.ts +1 -0
  18. package/dist/lib/assert.spec.js +41 -0
  19. package/dist/{src → lib}/command-parser/expand-arguments.d.ts +7 -7
  20. package/dist/lib/command-parser/expand-arguments.js +36 -0
  21. package/dist/lib/command-parser/expand-arguments.spec.d.ts +1 -0
  22. package/dist/lib/command-parser/expand-arguments.spec.js +57 -0
  23. package/dist/{src → lib}/command-parser/expand-shortcut.d.ts +2 -2
  24. package/dist/{src → lib}/command-parser/expand-shortcut.js +1 -5
  25. package/dist/lib/command-parser/expand-shortcut.spec.d.ts +1 -0
  26. package/dist/lib/command-parser/expand-shortcut.spec.js +36 -0
  27. package/dist/{src → lib}/command-parser/expand-wildcard.d.ts +2 -2
  28. package/dist/{src → lib}/command-parser/expand-wildcard.js +25 -31
  29. package/dist/lib/command-parser/expand-wildcard.spec.d.ts +1 -0
  30. package/dist/lib/command-parser/expand-wildcard.spec.js +288 -0
  31. package/dist/{src → lib}/command.d.ts +7 -10
  32. package/dist/{src → lib}/command.js +13 -32
  33. package/dist/lib/command.spec.d.ts +1 -0
  34. package/dist/lib/command.spec.js +369 -0
  35. package/dist/{src → lib}/completion-listener.d.ts +2 -3
  36. package/dist/{src → lib}/completion-listener.js +9 -36
  37. package/dist/lib/completion-listener.spec.d.ts +1 -0
  38. package/dist/lib/completion-listener.spec.js +229 -0
  39. package/dist/{src → lib}/concurrently.d.ts +10 -12
  40. package/dist/{src → lib}/concurrently.js +34 -47
  41. package/dist/lib/concurrently.spec.d.ts +1 -0
  42. package/dist/lib/concurrently.spec.js +320 -0
  43. package/dist/{src → lib}/date-format.d.ts +2 -2
  44. package/dist/{src → lib}/date-format.js +101 -77
  45. package/dist/lib/date-format.spec.d.ts +1 -0
  46. package/dist/lib/date-format.spec.js +480 -0
  47. package/dist/{src → lib}/defaults.d.ts +2 -6
  48. package/dist/{src → lib}/defaults.js +16 -23
  49. package/dist/{src → lib}/flow-control/input-handler.d.ts +4 -5
  50. package/dist/{src → lib}/flow-control/input-handler.js +8 -34
  51. package/dist/lib/flow-control/input-handler.spec.d.ts +1 -0
  52. package/dist/lib/flow-control/input-handler.spec.js +116 -0
  53. package/dist/{src → lib}/flow-control/kill-on-signal.d.ts +3 -5
  54. package/dist/{src → lib}/flow-control/kill-on-signal.js +3 -7
  55. package/dist/lib/flow-control/kill-on-signal.spec.d.ts +1 -0
  56. package/dist/lib/flow-control/kill-on-signal.spec.js +63 -0
  57. package/dist/{src → lib}/flow-control/kill-others.d.ts +3 -4
  58. package/dist/{src → lib}/flow-control/kill-others.js +8 -15
  59. package/dist/lib/flow-control/kill-others.spec.d.ts +1 -0
  60. package/dist/lib/flow-control/kill-others.spec.js +98 -0
  61. package/dist/{src → lib}/flow-control/log-error.d.ts +3 -3
  62. package/dist/{src → lib}/flow-control/log-error.js +1 -5
  63. package/dist/lib/flow-control/log-error.spec.d.ts +1 -0
  64. package/dist/lib/flow-control/log-error.spec.js +33 -0
  65. package/dist/{src → lib}/flow-control/log-exit.d.ts +3 -3
  66. package/dist/{src → lib}/flow-control/log-exit.js +1 -5
  67. package/dist/lib/flow-control/log-exit.spec.d.ts +1 -0
  68. package/dist/lib/flow-control/log-exit.spec.js +24 -0
  69. package/dist/{src → lib}/flow-control/log-output.d.ts +3 -3
  70. package/dist/{src → lib}/flow-control/log-output.js +1 -5
  71. package/dist/lib/flow-control/log-output.spec.d.ts +1 -0
  72. package/dist/lib/flow-control/log-output.spec.js +33 -0
  73. package/dist/{src → lib}/flow-control/log-timings.d.ts +3 -3
  74. package/dist/{src → lib}/flow-control/log-timings.js +11 -44
  75. package/dist/lib/flow-control/log-timings.spec.d.ts +1 -0
  76. package/dist/lib/flow-control/log-timings.spec.js +89 -0
  77. package/dist/{src → lib}/flow-control/logger-padding.d.ts +3 -3
  78. package/dist/{src → lib}/flow-control/logger-padding.js +7 -7
  79. package/dist/lib/flow-control/logger-padding.spec.d.ts +1 -0
  80. package/dist/lib/flow-control/logger-padding.spec.js +60 -0
  81. package/dist/{src → lib}/flow-control/output-error-handler.d.ts +4 -6
  82. package/dist/{src → lib}/flow-control/output-error-handler.js +3 -7
  83. package/dist/lib/flow-control/output-error-handler.spec.d.ts +1 -0
  84. package/dist/lib/flow-control/output-error-handler.spec.js +41 -0
  85. package/dist/{src → lib}/flow-control/restart-process.d.ts +4 -4
  86. package/dist/{src → lib}/flow-control/restart-process.js +10 -37
  87. package/dist/lib/flow-control/restart-process.spec.d.ts +1 -0
  88. package/dist/lib/flow-control/restart-process.spec.js +127 -0
  89. package/dist/{src → lib}/flow-control/teardown.d.ts +4 -5
  90. package/dist/{src → lib}/flow-control/teardown.js +6 -33
  91. package/dist/lib/flow-control/teardown.spec.d.ts +1 -0
  92. package/dist/lib/flow-control/teardown.spec.js +93 -0
  93. package/dist/{src → lib}/index.d.ts +21 -20
  94. package/dist/lib/index.js +98 -0
  95. package/dist/lib/jsonc.d.ts +8 -0
  96. package/dist/{src → lib}/jsonc.js +3 -8
  97. package/dist/lib/jsonc.spec.d.ts +1 -0
  98. package/dist/lib/jsonc.spec.js +73 -0
  99. package/dist/{src → lib}/logger.d.ts +3 -2
  100. package/dist/{src → lib}/logger.js +112 -53
  101. package/dist/lib/logger.spec.d.ts +1 -0
  102. package/dist/lib/logger.spec.js +507 -0
  103. package/dist/{src → lib}/observables.d.ts +1 -2
  104. package/dist/{src → lib}/observables.js +3 -7
  105. package/dist/lib/observables.spec.d.ts +1 -0
  106. package/dist/lib/observables.spec.js +29 -0
  107. package/dist/{src → lib}/output-writer.d.ts +3 -4
  108. package/dist/{src → lib}/output-writer.js +4 -31
  109. package/dist/lib/output-writer.spec.d.ts +1 -0
  110. package/dist/lib/output-writer.spec.js +96 -0
  111. package/dist/lib/prefix-color-selector.d.ts +21 -0
  112. package/dist/{src → lib}/prefix-color-selector.js +14 -31
  113. package/dist/lib/prefix-color-selector.spec.d.ts +1 -0
  114. package/dist/lib/prefix-color-selector.spec.js +159 -0
  115. package/dist/{src → lib}/spawn.d.ts +19 -16
  116. package/dist/lib/spawn.js +105 -0
  117. package/dist/lib/spawn.spec.d.ts +1 -0
  118. package/dist/lib/spawn.spec.js +100 -0
  119. package/dist/lib/utils.d.ts +25 -0
  120. package/dist/lib/utils.js +52 -0
  121. package/dist/lib/utils.spec.d.ts +1 -0
  122. package/dist/lib/utils.spec.js +58 -0
  123. package/dist/tsconfig.tsbuildinfo +1 -0
  124. package/docs/README.md +6 -0
  125. package/docs/cli/passthrough-arguments.md +8 -8
  126. package/docs/cli/prefixing.md +44 -4
  127. package/docs/cli/shortcuts.md +2 -2
  128. package/docs/shell-resolution.md +48 -0
  129. package/package.json +64 -85
  130. package/dist/bin/read-package.d.ts +0 -6
  131. package/dist/bin/read-package.js +0 -47
  132. package/dist/src/assert.d.ts +0 -5
  133. package/dist/src/assert.js +0 -16
  134. package/dist/src/command-parser/command-parser.d.ts +0 -19
  135. package/dist/src/command-parser/command-parser.js +0 -2
  136. package/dist/src/command-parser/expand-arguments.js +0 -39
  137. package/dist/src/command-parser/strip-quotes.d.ts +0 -16
  138. package/dist/src/command-parser/strip-quotes.js +0 -17
  139. package/dist/src/flow-control/flow-controller.d.ts +0 -13
  140. package/dist/src/flow-control/flow-controller.js +0 -2
  141. package/dist/src/index.js +0 -99
  142. package/dist/src/jsonc.d.ts +0 -8
  143. package/dist/src/prefix-color-selector.d.ts +0 -11
  144. package/dist/src/spawn.js +0 -49
  145. package/index.d.mts +0 -7
  146. package/index.d.ts +0 -11
  147. package/index.js +0 -14
  148. package/index.mjs +0 -10
  149. /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
- /** A list of colors that are readable in a terminal. */
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
- // Colors picked randomly, can be amended if required
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
- /// <reference types="node" />
2
- /// <reference types="node" />
3
- /// <reference types="node" />
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
- * Spawns a command using `cmd.exe` on Windows, or `/bin/sh` elsewhere.
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 spawn(command: string, options: SpawnOptions, spawn?: (command: string, args: string[], options: SpawnOptions) => ChildProcess, process?: Pick<NodeJS.Process, 'platform'>): ChildProcess;
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?: false | Pick<supportsColor.supportsColor.Level, "level"> | undefined;
23
+ colorSupport?: Pick<ColorSupport, "level"> | false;
21
24
  /**
22
25
  * The NodeJS process.
23
26
  */
24
- process?: Pick<NodeJS.Process, "platform" | "cwd" | "env"> | undefined;
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 | undefined;
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 | undefined;
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?: "raw" | "hidden" | "normal" | undefined;
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> | undefined;
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 {};