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
@@ -1,10 +1,8 @@
1
- /// <reference types="node" />
2
- /// <reference types="node" />
3
- import { Writable } from 'stream';
4
- import { CloseEvent, Command, CommandIdentifier, CommandInfo, KillProcess, SpawnCommand } from './command';
5
- import { SuccessCondition } from './completion-listener';
6
- import { FlowController } from './flow-control/flow-controller';
7
- import { Logger } from './logger';
1
+ import { Writable } from 'node:stream';
2
+ import { CloseEvent, Command, CommandIdentifier, CommandInfo, KillProcess, SpawnCommand } from './command.js';
3
+ import { SuccessCondition } from './completion-listener.js';
4
+ import { FlowController } from './flow-control/flow-controller.js';
5
+ import { Logger } from './logger.js';
8
6
  /**
9
7
  * A command that is to be passed into `concurrently()`.
10
8
  * If value is a string, then that's the command's command line.
@@ -13,7 +11,7 @@ import { Logger } from './logger';
13
11
  export type ConcurrentlyCommandInput = string | ({
14
12
  command: string;
15
13
  } & Partial<CommandInfo>);
16
- export type ConcurrentlyResult = {
14
+ export interface ConcurrentlyResult {
17
15
  /**
18
16
  * All commands created and ran by concurrently.
19
17
  */
@@ -26,8 +24,8 @@ export type ConcurrentlyResult = {
26
24
  * spawned; commands that didn't spawn are filtered out.
27
25
  */
28
26
  result: Promise<CloseEvent[]>;
29
- };
30
- export type ConcurrentlyOptions = {
27
+ }
28
+ export interface ConcurrentlyOptions {
31
29
  logger?: Logger;
32
30
  /**
33
31
  * Which stream should the commands output be written to.
@@ -38,7 +36,7 @@ export type ConcurrentlyOptions = {
38
36
  */
39
37
  group?: boolean;
40
38
  /**
41
- * A comma-separated list of chalk colors or a string for available styles listed below to use on prefixes.
39
+ * A comma-separated list of Chalk colors or a string for available styles listed below to use on prefixes.
42
40
  * If there are more commands than colors, the last color will be repeated.
43
41
  *
44
42
  * Available modifiers:
@@ -108,7 +106,7 @@ export type ConcurrentlyOptions = {
108
106
  * @see ExpandArguments
109
107
  */
110
108
  additionalArguments?: string[];
111
- };
109
+ }
112
110
  /**
113
111
  * Core concurrently functionality -- spawns the given commands concurrently and
114
112
  * returns the commands themselves + the result according to the specified success condition.
@@ -1,26 +1,19 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.concurrently = void 0;
7
- const assert_1 = __importDefault(require("assert"));
8
- const lodash_1 = __importDefault(require("lodash"));
9
- const os_1 = require("os");
10
- const rxjs_1 = require("rxjs");
11
- const tree_kill_1 = __importDefault(require("tree-kill"));
12
- const command_1 = require("./command");
13
- const expand_arguments_1 = require("./command-parser/expand-arguments");
14
- const expand_shortcut_1 = require("./command-parser/expand-shortcut");
15
- const expand_wildcard_1 = require("./command-parser/expand-wildcard");
16
- const strip_quotes_1 = require("./command-parser/strip-quotes");
17
- const completion_listener_1 = require("./completion-listener");
18
- const output_writer_1 = require("./output-writer");
19
- const prefix_color_selector_1 = require("./prefix-color-selector");
20
- const spawn_1 = require("./spawn");
1
+ import assert from 'node:assert';
2
+ import os from 'node:os';
3
+ import { takeUntil } from 'rxjs';
4
+ import treeKill from 'tree-kill';
5
+ import { Command, } from './command.js';
6
+ import { ExpandArguments } from './command-parser/expand-arguments.js';
7
+ import { ExpandShortcut } from './command-parser/expand-shortcut.js';
8
+ import { ExpandWildcard } from './command-parser/expand-wildcard.js';
9
+ import { CompletionListener } from './completion-listener.js';
10
+ import { OutputWriter } from './output-writer.js';
11
+ import { PrefixColorSelector } from './prefix-color-selector.js';
12
+ import { createSpawn, getSpawnOpts } from './spawn.js';
13
+ import { castArray } from './utils.js';
21
14
  const defaults = {
22
- spawn: spawn_1.spawn,
23
- kill: tree_kill_1.default,
15
+ spawn: createSpawn(),
16
+ kill: treeKill,
24
17
  raw: false,
25
18
  controllers: [],
26
19
  cwd: undefined,
@@ -31,64 +24,59 @@ const defaults = {
31
24
  *
32
25
  * @see CompletionListener
33
26
  */
34
- function concurrently(baseCommands, baseOptions) {
35
- assert_1.default.ok(Array.isArray(baseCommands), '[concurrently] commands should be an array');
36
- assert_1.default.notStrictEqual(baseCommands.length, 0, '[concurrently] no commands provided');
37
- const options = lodash_1.default.defaults(baseOptions, defaults);
38
- const prefixColorSelector = new prefix_color_selector_1.PrefixColorSelector(options.prefixColors || []);
39
- const commandParsers = [
40
- new strip_quotes_1.StripQuotes(),
41
- new expand_shortcut_1.ExpandShortcut(),
42
- new expand_wildcard_1.ExpandWildcard(),
43
- ];
27
+ export function concurrently(baseCommands, baseOptions) {
28
+ assert.ok(Array.isArray(baseCommands), '[concurrently] commands should be an array');
29
+ assert.notStrictEqual(baseCommands.length, 0, '[concurrently] no commands provided');
30
+ const options = { ...defaults, ...baseOptions };
31
+ const prefixColorSelector = new PrefixColorSelector(options.prefixColors || []);
32
+ const commandParsers = [new ExpandShortcut(), new ExpandWildcard()];
44
33
  if (options.additionalArguments) {
45
- commandParsers.push(new expand_arguments_1.ExpandArguments(options.additionalArguments));
34
+ commandParsers.push(new ExpandArguments(options.additionalArguments));
46
35
  }
47
36
  const hide = (options.hide || []).map(String);
48
- let commands = (0, lodash_1.default)(baseCommands)
37
+ let commands = baseCommands
49
38
  .map(mapToCommandInfo)
50
39
  .flatMap((command) => parseCommand(command, commandParsers))
51
40
  .map((command, index) => {
52
41
  const hidden = hide.includes(command.name) || hide.includes(String(index));
53
- return new command_1.Command({
42
+ return new Command({
54
43
  index,
55
44
  prefixColor: prefixColorSelector.getNextColor(),
56
45
  ...command,
57
- }, (0, spawn_1.getSpawnOpts)({
46
+ }, getSpawnOpts({
58
47
  ipc: command.ipc,
59
- stdio: hidden ? 'hidden' : command.raw ?? options.raw ? 'raw' : 'normal',
48
+ stdio: hidden ? 'hidden' : (command.raw ?? options.raw) ? 'raw' : 'normal',
60
49
  env: command.env,
61
50
  cwd: command.cwd || options.cwd,
62
51
  }), options.spawn, options.kill);
63
- })
64
- .value();
52
+ });
65
53
  const handleResult = options.controllers.reduce(({ commands: prevCommands, onFinishCallbacks }, controller) => {
66
54
  const { commands, onFinish } = controller.handle(prevCommands);
67
55
  return {
68
56
  commands,
69
- onFinishCallbacks: lodash_1.default.concat(onFinishCallbacks, onFinish ? [onFinish] : []),
57
+ onFinishCallbacks: onFinishCallbacks.concat(onFinish ? [onFinish] : []),
70
58
  };
71
59
  }, { commands, onFinishCallbacks: [] });
72
60
  commands = handleResult.commands;
73
61
  if (options.logger && options.outputStream) {
74
- const outputWriter = new output_writer_1.OutputWriter({
62
+ const outputWriter = new OutputWriter({
75
63
  outputStream: options.outputStream,
76
64
  group: !!options.group,
77
65
  commands,
78
66
  });
79
67
  options.logger.output
80
68
  // Stop trying to write after there's been an error.
81
- .pipe((0, rxjs_1.takeUntil)(outputWriter.error))
69
+ .pipe(takeUntil(outputWriter.error))
82
70
  .subscribe(({ command, text }) => outputWriter.write(command, text));
83
71
  }
84
72
  const commandsLeft = commands.slice();
85
73
  const maxProcesses = Math.max(1, (typeof options.maxProcesses === 'string' && options.maxProcesses.endsWith('%')
86
- ? Math.round(((0, os_1.cpus)().length * Number(options.maxProcesses.slice(0, -1))) / 100)
74
+ ? Math.round((os.cpus().length * Number(options.maxProcesses.slice(0, -1))) / 100)
87
75
  : Number(options.maxProcesses)) || commandsLeft.length);
88
76
  for (let i = 0; i < maxProcesses; i++) {
89
77
  maybeRunMore(commandsLeft, options.abortSignal);
90
78
  }
91
- const result = new completion_listener_1.CompletionListener({ successCondition: options.successCondition })
79
+ const result = new CompletionListener({ successCondition: options.successCondition })
92
80
  .listen(commands, options.abortSignal)
93
81
  .finally(() => Promise.all(handleResult.onFinishCallbacks.map((onFinish) => onFinish())));
94
82
  return {
@@ -96,12 +84,11 @@ function concurrently(baseCommands, baseOptions) {
96
84
  commands,
97
85
  };
98
86
  }
99
- exports.concurrently = concurrently;
100
87
  function mapToCommandInfo(command) {
101
88
  if (typeof command === 'string') {
102
89
  return mapToCommandInfo({ command });
103
90
  }
104
- assert_1.default.ok(command.command, '[concurrently] command cannot be empty');
91
+ assert.ok(command.command, '[concurrently] command cannot be empty');
105
92
  return {
106
93
  command: command.command,
107
94
  name: command.name || '',
@@ -121,7 +108,7 @@ function mapToCommandInfo(command) {
121
108
  };
122
109
  }
123
110
  function parseCommand(command, parsers) {
124
- return parsers.reduce((commands, parser) => lodash_1.default.flatMap(commands, (command) => parser.parse(command)), lodash_1.default.castArray(command));
111
+ return parsers.reduce((commands, parser) => commands.flatMap((command) => parser.parse(command)), castArray(command));
125
112
  }
126
113
  function maybeRunMore(commandsLeft, abortSignal) {
127
114
  const command = commandsLeft.shift();
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,320 @@
1
+ import os from 'node:os';
2
+ import { Writable } from 'node:stream';
3
+ import { beforeEach, expect, it, vi } from 'vitest';
4
+ import { createMockInstance } from './__fixtures__/create-mock-instance.js';
5
+ import { createFakeProcess, FakeCommand } from './__fixtures__/fake-command.js';
6
+ import { concurrently } from './concurrently.js';
7
+ import { Logger } from './logger.js';
8
+ let spawn;
9
+ let kill;
10
+ let onFinishHooks;
11
+ let controllers;
12
+ let processes;
13
+ const create = (commands, options = {}) => concurrently(commands, Object.assign(options, { controllers, spawn, kill }));
14
+ beforeEach(() => {
15
+ vi.resetAllMocks();
16
+ processes = [];
17
+ spawn = vi.fn(() => {
18
+ const process = createFakeProcess(processes.length);
19
+ processes.push(process);
20
+ return process;
21
+ });
22
+ kill = vi.fn();
23
+ onFinishHooks = [vi.fn(), vi.fn()];
24
+ controllers = [
25
+ { handle: vi.fn((commands) => ({ commands, onFinish: onFinishHooks[0] })) },
26
+ { handle: vi.fn((commands) => ({ commands, onFinish: onFinishHooks[1] })) },
27
+ ];
28
+ });
29
+ it('fails if commands is not an array', () => {
30
+ const bomb = () => create('foo');
31
+ expect(bomb).toThrow();
32
+ });
33
+ it('fails if no commands were provided', () => {
34
+ const bomb = () => create([]);
35
+ expect(bomb).toThrow();
36
+ });
37
+ it('spawns all commands', () => {
38
+ create(['echo', 'kill']);
39
+ expect(spawn).toHaveBeenCalledTimes(2);
40
+ expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({}));
41
+ expect(spawn).toHaveBeenCalledWith('kill', expect.objectContaining({}));
42
+ });
43
+ it('log output is passed to output stream if logger is specified in options', () => {
44
+ const logger = new Logger({ hide: [] });
45
+ const outputStream = createMockInstance(Writable);
46
+ create(['foo'], { logger, outputStream });
47
+ logger.log('foo', 'bar');
48
+ expect(outputStream.write).toHaveBeenCalledTimes(2);
49
+ expect(outputStream.write).toHaveBeenCalledWith('foo');
50
+ expect(outputStream.write).toHaveBeenCalledWith('bar');
51
+ });
52
+ it('log output is not passed to output stream after it has errored', () => {
53
+ const logger = new Logger({ hide: [] });
54
+ const outputStream = new Writable();
55
+ vi.spyOn(outputStream, 'write');
56
+ create(['foo'], { logger, outputStream });
57
+ outputStream.emit('error', new Error('test'));
58
+ logger.log('foo', 'bar');
59
+ expect(outputStream.write).not.toHaveBeenCalled();
60
+ });
61
+ it('spawns commands up to configured limit at once', () => {
62
+ create(['foo', 'bar', 'baz', 'qux'], { maxProcesses: 2 });
63
+ expect(spawn).toHaveBeenCalledTimes(2);
64
+ expect(spawn).toHaveBeenCalledWith('foo', expect.objectContaining({}));
65
+ expect(spawn).toHaveBeenCalledWith('bar', expect.objectContaining({}));
66
+ // Test out of order completion picking up new processes in-order
67
+ processes[1].emit('close', 1, null);
68
+ expect(spawn).toHaveBeenCalledTimes(3);
69
+ expect(spawn).toHaveBeenCalledWith('baz', expect.objectContaining({}));
70
+ processes[0].emit('close', null, 'SIGINT');
71
+ expect(spawn).toHaveBeenCalledTimes(4);
72
+ expect(spawn).toHaveBeenCalledWith('qux', expect.objectContaining({}));
73
+ // Shouldn't attempt to spawn anything else.
74
+ processes[2].emit('close', 1, null);
75
+ expect(spawn).toHaveBeenCalledTimes(4);
76
+ });
77
+ it('spawns commands up to percent based limit at once', () => {
78
+ // Mock architecture with 4 cores
79
+ const cpusSpy = vi.spyOn(os, 'cpus');
80
+ cpusSpy.mockReturnValue(Array.from({ length: 4 }).fill({
81
+ model: 'Intel',
82
+ speed: 0,
83
+ times: { user: 0, nice: 0, sys: 0, idle: 0, irq: 0 },
84
+ }));
85
+ create(['foo', 'bar', 'baz', 'qux'], { maxProcesses: '50%' });
86
+ // Max parallel processes should be 2 (50% of 4 cores)
87
+ expect(spawn).toHaveBeenCalledTimes(2);
88
+ expect(spawn).toHaveBeenCalledWith('foo', expect.objectContaining({}));
89
+ expect(spawn).toHaveBeenCalledWith('bar', expect.objectContaining({}));
90
+ // Close first process and expect third to be spawned
91
+ processes[0].emit('close', 1, null);
92
+ expect(spawn).toHaveBeenCalledTimes(3);
93
+ expect(spawn).toHaveBeenCalledWith('baz', expect.objectContaining({}));
94
+ // Close second process and expect fourth to be spawned
95
+ processes[1].emit('close', 1, null);
96
+ expect(spawn).toHaveBeenCalledTimes(4);
97
+ expect(spawn).toHaveBeenCalledWith('qux', expect.objectContaining({}));
98
+ });
99
+ it('does not spawn further commands on abort signal aborted', () => {
100
+ const abortController = new AbortController();
101
+ create(['foo', 'bar'], { maxProcesses: 1, abortSignal: abortController.signal });
102
+ expect(spawn).toHaveBeenCalledTimes(1);
103
+ abortController.abort();
104
+ processes[0].emit('close', 0, null);
105
+ expect(spawn).toHaveBeenCalledTimes(1);
106
+ });
107
+ it('preserves quotes in well-formed shell commands in the library API', () => {
108
+ create(['"/usr/local/bin/mytool" --flag "some value"']);
109
+ controllers.forEach((controller) => {
110
+ expect(controller.handle).toHaveBeenCalledWith([
111
+ expect.objectContaining({
112
+ command: '"/usr/local/bin/mytool" --flag "some value"',
113
+ index: 0,
114
+ }),
115
+ ]);
116
+ });
117
+ });
118
+ it('passes commands with multiple quote sets through unchanged in the library API', () => {
119
+ create(['"/usr/local/bin/mytool" --flag "some value" --other "last arg"']);
120
+ controllers.forEach((controller) => {
121
+ expect(controller.handle).toHaveBeenCalledWith([
122
+ expect.objectContaining({
123
+ command: '"/usr/local/bin/mytool" --flag "some value" --other "last arg"',
124
+ index: 0,
125
+ }),
126
+ ]);
127
+ });
128
+ });
129
+ it('runs commands with a name or prefix color', () => {
130
+ create([{ command: 'echo', prefixColor: 'red', name: 'foo' }, 'kill']);
131
+ controllers.forEach((controller) => {
132
+ expect(controller.handle).toHaveBeenCalledWith([
133
+ expect.objectContaining({ command: 'echo', index: 0, name: 'foo', prefixColor: 'red' }),
134
+ expect.objectContaining({ command: 'kill', index: 1, name: '', prefixColor: '' }),
135
+ ]);
136
+ });
137
+ });
138
+ it('runs commands with a list of colors', () => {
139
+ create(['echo', 'kill'], {
140
+ prefixColors: ['red'],
141
+ });
142
+ controllers.forEach((controller) => {
143
+ expect(controller.handle).toHaveBeenCalledWith([
144
+ expect.objectContaining({ command: 'echo', prefixColor: 'red' }),
145
+ expect.objectContaining({ command: 'kill', prefixColor: 'red' }),
146
+ ]);
147
+ });
148
+ });
149
+ it('passes commands wrapped from a controller to the next one', () => {
150
+ const fakeCommand = new FakeCommand('banana', 'banana');
151
+ controllers[0].handle.mockReturnValue({ commands: [fakeCommand] });
152
+ create(['echo']);
153
+ expect(controllers[0].handle).toHaveBeenCalledWith([
154
+ expect.objectContaining({ command: 'echo', index: 0 }),
155
+ ]);
156
+ expect(controllers[1].handle).toHaveBeenCalledWith([fakeCommand]);
157
+ expect(fakeCommand.start).toHaveBeenCalledTimes(1);
158
+ });
159
+ it('merges extra env vars into each command', () => {
160
+ create([
161
+ { command: 'echo', env: { foo: 'bar' } },
162
+ { command: 'echo', env: { foo: 'baz' } },
163
+ 'kill',
164
+ ]);
165
+ expect(spawn).toHaveBeenCalledTimes(3);
166
+ expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({
167
+ env: expect.objectContaining({ foo: 'bar' }),
168
+ }));
169
+ expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({
170
+ env: expect.objectContaining({ foo: 'baz' }),
171
+ }));
172
+ expect(spawn).toHaveBeenCalledWith('kill', expect.objectContaining({
173
+ env: expect.not.objectContaining({ foo: expect.anything() }),
174
+ }));
175
+ });
176
+ it('uses cwd from options for each command', () => {
177
+ create([
178
+ { command: 'echo', env: { foo: 'bar' } },
179
+ { command: 'echo', env: { foo: 'baz' } },
180
+ 'kill',
181
+ ], {
182
+ cwd: 'foobar',
183
+ });
184
+ expect(spawn).toHaveBeenCalledTimes(3);
185
+ expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({
186
+ env: expect.objectContaining({ foo: 'bar' }),
187
+ cwd: 'foobar',
188
+ }));
189
+ expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({
190
+ env: expect.objectContaining({ foo: 'baz' }),
191
+ cwd: 'foobar',
192
+ }));
193
+ expect(spawn).toHaveBeenCalledWith('kill', expect.objectContaining({
194
+ env: expect.not.objectContaining({ foo: expect.anything() }),
195
+ cwd: 'foobar',
196
+ }));
197
+ });
198
+ it('uses overridden cwd option for each command if specified', () => {
199
+ create([
200
+ { command: 'echo', env: { foo: 'bar' }, cwd: 'baz' },
201
+ { command: 'echo', env: { foo: 'baz' } },
202
+ ], {
203
+ cwd: 'foobar',
204
+ });
205
+ expect(spawn).toHaveBeenCalledTimes(2);
206
+ expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({
207
+ env: expect.objectContaining({ foo: 'bar' }),
208
+ cwd: 'baz',
209
+ }));
210
+ expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({
211
+ env: expect.objectContaining({ foo: 'baz' }),
212
+ cwd: 'foobar',
213
+ }));
214
+ });
215
+ it('uses raw from options for each command', () => {
216
+ create([{ command: 'echo' }, 'kill'], {
217
+ raw: true,
218
+ });
219
+ expect(spawn).toHaveBeenCalledTimes(2);
220
+ expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({
221
+ stdio: ['inherit', 'inherit', 'inherit'],
222
+ }));
223
+ expect(spawn).toHaveBeenCalledWith('kill', expect.objectContaining({
224
+ stdio: ['inherit', 'inherit', 'inherit'],
225
+ }));
226
+ });
227
+ it('uses overridden raw option for each command if specified', () => {
228
+ create([{ command: 'echo', raw: false }, { command: 'echo' }], {
229
+ raw: true,
230
+ });
231
+ expect(spawn).toHaveBeenCalledTimes(2);
232
+ expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({
233
+ stdio: ['pipe', 'pipe', 'pipe'],
234
+ }));
235
+ expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({
236
+ stdio: ['inherit', 'inherit', 'inherit'],
237
+ }));
238
+ });
239
+ it('uses hide from options for each command', () => {
240
+ create([{ command: 'echo' }, 'kill'], {
241
+ hide: [1],
242
+ });
243
+ expect(spawn).toHaveBeenCalledTimes(2);
244
+ expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({
245
+ stdio: ['pipe', 'pipe', 'pipe'],
246
+ }));
247
+ expect(spawn).toHaveBeenCalledWith('kill', expect.objectContaining({
248
+ stdio: ['pipe', 'ignore', 'ignore'],
249
+ }));
250
+ });
251
+ it('hides output for commands even if raw option is on', () => {
252
+ create([{ command: 'echo' }, 'kill'], {
253
+ hide: [1],
254
+ raw: true,
255
+ });
256
+ expect(spawn).toHaveBeenCalledTimes(2);
257
+ expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({
258
+ stdio: ['inherit', 'inherit', 'inherit'],
259
+ }));
260
+ expect(spawn).toHaveBeenCalledWith('kill', expect.objectContaining({
261
+ stdio: ['pipe', 'ignore', 'ignore'],
262
+ }));
263
+ });
264
+ it('argument placeholders are properly replaced when additional arguments are passed', () => {
265
+ create([
266
+ { command: 'echo {1}' },
267
+ { command: 'echo {@}' },
268
+ { command: 'echo {*}' },
269
+ { command: 'echo \\{@}' },
270
+ ], {
271
+ additionalArguments: ['foo', 'bar'],
272
+ });
273
+ expect(spawn).toHaveBeenCalledTimes(4);
274
+ expect(spawn).toHaveBeenCalledWith('echo foo', expect.objectContaining({}));
275
+ expect(spawn).toHaveBeenCalledWith('echo foo bar', expect.objectContaining({}));
276
+ expect(spawn).toHaveBeenCalledWith("echo 'foo bar'", expect.objectContaining({}));
277
+ expect(spawn).toHaveBeenCalledWith('echo {@}', expect.objectContaining({}));
278
+ });
279
+ it('argument placeholders are not replaced when additional arguments are not defined', () => {
280
+ create([
281
+ { command: 'echo {1}' },
282
+ { command: 'echo {@}' },
283
+ { command: 'echo {*}' },
284
+ { command: 'echo \\{@}' },
285
+ ]);
286
+ expect(spawn).toHaveBeenCalledTimes(4);
287
+ expect(spawn).toHaveBeenCalledWith('echo {1}', expect.objectContaining({}));
288
+ expect(spawn).toHaveBeenCalledWith('echo {@}', expect.objectContaining({}));
289
+ expect(spawn).toHaveBeenCalledWith('echo {*}', expect.objectContaining({}));
290
+ expect(spawn).toHaveBeenCalledWith('echo {@}', expect.objectContaining({}));
291
+ });
292
+ it('runs onFinish hook after all commands run', async () => {
293
+ const promise = create(['foo', 'bar'], { maxProcesses: 1 });
294
+ expect(spawn).toHaveBeenCalledTimes(1);
295
+ expect(onFinishHooks[0]).not.toHaveBeenCalled();
296
+ expect(onFinishHooks[1]).not.toHaveBeenCalled();
297
+ processes[0].emit('close', 0, null);
298
+ expect(spawn).toHaveBeenCalledTimes(2);
299
+ expect(onFinishHooks[0]).not.toHaveBeenCalled();
300
+ expect(onFinishHooks[1]).not.toHaveBeenCalled();
301
+ processes[1].emit('close', 0, null);
302
+ await promise.result;
303
+ expect(onFinishHooks[0]).toHaveBeenCalled();
304
+ expect(onFinishHooks[1]).toHaveBeenCalled();
305
+ });
306
+ // This test should time out if broken
307
+ it('waits for onFinish hooks to complete before resolving', async () => {
308
+ onFinishHooks[0].mockResolvedValue(undefined);
309
+ const { result } = create(['foo', 'bar']);
310
+ processes[0].emit('close', 0, null);
311
+ processes[1].emit('close', 0, null);
312
+ await expect(result).resolves.toBeDefined();
313
+ });
314
+ it('rejects if onFinish hooks reject', async () => {
315
+ onFinishHooks[0].mockRejectedValue('error');
316
+ const { result } = create(['foo', 'bar']);
317
+ processes[0].emit('close', 0, null);
318
+ processes[1].emit('close', 0, null);
319
+ await expect(result).rejects.toBe('error');
320
+ });
@@ -1,7 +1,7 @@
1
- export type FormatterOptions = {
1
+ export interface FormatterOptions {
2
2
  locale?: string;
3
3
  calendar?: string;
4
- };
4
+ }
5
5
  /**
6
6
  * Unicode-compliant date/time formatter.
7
7
  *