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,42 +1,12 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
25
- var __importDefault = (this && this.__importDefault) || function (mod) {
26
- return (mod && mod.__esModule) ? mod : { "default": mod };
27
- };
28
- Object.defineProperty(exports, "__esModule", { value: true });
29
- exports.LogTimings = void 0;
30
- const assert = __importStar(require("assert"));
31
- const lodash_1 = __importDefault(require("lodash"));
32
- const Rx = __importStar(require("rxjs"));
33
- const operators_1 = require("rxjs/operators");
34
- const date_format_1 = require("../date-format");
35
- const defaults = __importStar(require("../defaults"));
1
+ import assert from 'node:assert';
2
+ import Rx from 'rxjs';
3
+ import { bufferCount, combineLatestWith, take } from 'rxjs/operators';
4
+ import { DateFormatter } from '../date-format.js';
5
+ import * as defaults from '../defaults.js';
36
6
  /**
37
7
  * Logs timing information about commands as they start/stop and then a summary when all commands finish.
38
8
  */
39
- class LogTimings {
9
+ export class LogTimings {
40
10
  static mapCloseEventToTimingInfo({ command, timings, killed, exitCode, }) {
41
11
  const readableDurationMs = (timings.endDate.getTime() - timings.startDate.getTime()).toLocaleString();
42
12
  return {
@@ -51,15 +21,13 @@ class LogTimings {
51
21
  dateFormatter;
52
22
  constructor({ logger, timestampFormat = defaults.timestampFormat, }) {
53
23
  this.logger = logger;
54
- this.dateFormatter = new date_format_1.DateFormatter(timestampFormat);
24
+ this.dateFormatter = new DateFormatter(timestampFormat);
55
25
  }
56
26
  printExitInfoTimingTable(exitInfos) {
57
27
  assert.ok(this.logger);
58
- const exitInfoTable = (0, lodash_1.default)(exitInfos)
59
- .sortBy(({ timings }) => timings.durationSeconds)
60
- .reverse()
61
- .map(LogTimings.mapCloseEventToTimingInfo)
62
- .value();
28
+ const exitInfoTable = exitInfos
29
+ .sort((a, b) => b.timings.durationSeconds - a.timings.durationSeconds)
30
+ .map(LogTimings.mapCloseEventToTimingInfo);
63
31
  this.logger.logGlobalEvent('Timings:');
64
32
  this.logger.logTable(exitInfoTable);
65
33
  return exitInfos;
@@ -86,9 +54,8 @@ class LogTimings {
86
54
  // overall summary timings
87
55
  const closeStreams = commands.map((command) => command.close);
88
56
  const finished = new Rx.Subject();
89
- const allProcessesClosed = Rx.merge(...closeStreams).pipe((0, operators_1.bufferCount)(closeStreams.length), (0, operators_1.take)(1), (0, operators_1.combineLatestWith)(finished));
57
+ const allProcessesClosed = Rx.merge(...closeStreams).pipe(bufferCount(closeStreams.length), take(1), combineLatestWith(finished));
90
58
  allProcessesClosed.subscribe(([exitInfos]) => this.printExitInfoTimingTable(exitInfos));
91
59
  return { commands, onFinish: () => finished.next() };
92
60
  }
93
61
  }
94
- exports.LogTimings = LogTimings;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,89 @@
1
+ import { beforeEach, expect, it } from 'vitest';
2
+ import { createMockInstance } from '../__fixtures__/create-mock-instance.js';
3
+ import { createFakeCloseEvent, FakeCommand } from '../__fixtures__/fake-command.js';
4
+ import { DateFormatter } from '../date-format.js';
5
+ import { Logger } from '../logger.js';
6
+ import { LogTimings } from './log-timings.js';
7
+ // shown in timing order
8
+ const startDate0 = new Date();
9
+ const startDate1 = new Date(startDate0.getTime() + 1000);
10
+ const endDate1 = new Date(startDate0.getTime() + 5000);
11
+ const endDate0 = new Date(startDate0.getTime() + 3000);
12
+ const timestampFormat = 'yyyy-MM-dd HH:mm:ss.SSS';
13
+ const getDurationText = (startDate, endDate) => `${(endDate.getTime() - startDate.getTime()).toLocaleString()}ms`;
14
+ const command0DurationTextMs = getDurationText(startDate0, endDate0);
15
+ const command1DurationTextMs = getDurationText(startDate1, endDate1);
16
+ let controller;
17
+ let logger;
18
+ let commands;
19
+ let command0ExitInfo;
20
+ let command1ExitInfo;
21
+ beforeEach(() => {
22
+ commands = [new FakeCommand('foo', 'command 1', 0), new FakeCommand('bar', 'command 2', 1)];
23
+ command0ExitInfo = createFakeCloseEvent({
24
+ command: commands[0],
25
+ timings: {
26
+ startDate: startDate0,
27
+ endDate: endDate0,
28
+ durationSeconds: endDate0.getTime() - startDate0.getTime(),
29
+ },
30
+ index: commands[0].index,
31
+ });
32
+ command1ExitInfo = createFakeCloseEvent({
33
+ command: commands[1],
34
+ timings: {
35
+ startDate: startDate1,
36
+ endDate: endDate1,
37
+ durationSeconds: endDate1.getTime() - startDate1.getTime(),
38
+ },
39
+ index: commands[1].index,
40
+ });
41
+ logger = createMockInstance(Logger);
42
+ controller = new LogTimings({ logger, timestampFormat });
43
+ });
44
+ it('returns same commands', () => {
45
+ expect(controller.handle(commands)).toMatchObject({ commands });
46
+ });
47
+ it("does not log timings and doesn't throw if no logger is provided", () => {
48
+ controller = new LogTimings({});
49
+ const { onFinish } = controller.handle(commands);
50
+ commands[0].timer.next({ startDate: startDate0 });
51
+ commands[1].timer.next({ startDate: startDate1 });
52
+ commands[1].timer.next({ startDate: startDate1, endDate: endDate1 });
53
+ commands[0].timer.next({ startDate: startDate0, endDate: endDate0 });
54
+ onFinish?.();
55
+ expect(logger.logCommandEvent).toHaveBeenCalledTimes(0);
56
+ });
57
+ it('logs the timings at the start and end (ie complete or error) event of each command', () => {
58
+ const formatter = new DateFormatter(timestampFormat);
59
+ controller.handle(commands);
60
+ commands[0].timer.next({ startDate: startDate0 });
61
+ commands[1].timer.next({ startDate: startDate1 });
62
+ commands[1].timer.next({ startDate: startDate1, endDate: endDate1 });
63
+ commands[0].timer.next({ startDate: startDate0, endDate: endDate0 });
64
+ expect(logger.logCommandEvent).toHaveBeenCalledTimes(4);
65
+ expect(logger.logCommandEvent).toHaveBeenCalledWith(`${commands[0].command} started at ${formatter.format(startDate0)}`, commands[0]);
66
+ expect(logger.logCommandEvent).toHaveBeenCalledWith(`${commands[1].command} started at ${formatter.format(startDate1)}`, commands[1]);
67
+ expect(logger.logCommandEvent).toHaveBeenCalledWith(`${commands[1].command} stopped at ${formatter.format(endDate1)} after ${command1DurationTextMs}`, commands[1]);
68
+ expect(logger.logCommandEvent).toHaveBeenCalledWith(`${commands[0].command} stopped at ${formatter.format(endDate0)} after ${command0DurationTextMs}`, commands[0]);
69
+ });
70
+ it('does not log timings summary if there was an error', () => {
71
+ const { onFinish } = controller.handle(commands);
72
+ commands[0].close.next(command0ExitInfo);
73
+ commands[1].error.next(undefined);
74
+ onFinish?.();
75
+ expect(logger.logTable).toHaveBeenCalledTimes(0);
76
+ });
77
+ it('logs the sorted timings summary when all processes close successfully after onFinish is called', () => {
78
+ const { onFinish } = controller.handle(commands);
79
+ commands[0].close.next(command0ExitInfo);
80
+ commands[1].close.next(command1ExitInfo);
81
+ expect(logger.logGlobalEvent).toHaveBeenCalledTimes(0);
82
+ onFinish?.();
83
+ expect(logger.logGlobalEvent).toHaveBeenCalledExactlyOnceWith('Timings:');
84
+ // sorted by duration
85
+ expect(logger.logTable).toHaveBeenCalledExactlyOnceWith([
86
+ LogTimings.mapCloseEventToTimingInfo(command1ExitInfo),
87
+ LogTimings.mapCloseEventToTimingInfo(command0ExitInfo),
88
+ ]);
89
+ });
@@ -1,6 +1,6 @@
1
- import { Command } from '../command';
2
- import { Logger } from '../logger';
3
- import { FlowController } from './flow-controller';
1
+ import { Command } from '../command.js';
2
+ import { Logger } from '../logger.js';
3
+ import { FlowController } from './flow-controller.js';
4
4
  export declare class LoggerPadding implements FlowController {
5
5
  private readonly logger;
6
6
  constructor({ logger }: {
@@ -1,7 +1,8 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.LoggerPadding = void 0;
4
- class LoggerPadding {
1
+ import { COLOR_MARKER_RE } from '../logger.js';
2
+ function visibleLength(value) {
3
+ return value ? value.replace(COLOR_MARKER_RE, '').length : 0;
4
+ }
5
+ export class LoggerPadding {
5
6
  logger;
6
7
  constructor({ logger }) {
7
8
  this.logger = logger;
@@ -11,7 +12,7 @@ class LoggerPadding {
11
12
  // Compute the prefix length now, which works for all styles but those with a PID.
12
13
  let length = commands.reduce((length, command) => {
13
14
  const content = this.logger.getPrefixContent(command);
14
- return Math.max(length, content?.value.length || 0);
15
+ return Math.max(length, visibleLength(content?.value));
15
16
  }, 0);
16
17
  this.logger.setPrefixLength(length);
17
18
  // The length of prefixes is somewhat stable, except for PIDs, which might change when a
@@ -20,7 +21,7 @@ class LoggerPadding {
20
21
  const subs = commands.map((command) => command.timer.subscribe((event) => {
21
22
  if (!event.endDate) {
22
23
  const content = this.logger.getPrefixContent(command);
23
- length = Math.max(length, content?.value.length || 0);
24
+ length = Math.max(length, visibleLength(content?.value));
24
25
  this.logger.setPrefixLength(length);
25
26
  }
26
27
  }));
@@ -32,4 +33,3 @@ class LoggerPadding {
32
33
  };
33
34
  }
34
35
  }
35
- exports.LoggerPadding = LoggerPadding;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,60 @@
1
+ import { beforeEach, expect, it } from 'vitest';
2
+ import { createMockInstance } from '../__fixtures__/create-mock-instance.js';
3
+ import { FakeCommand } from '../__fixtures__/fake-command.js';
4
+ import { Logger } from '../logger.js';
5
+ import { LoggerPadding } from './logger-padding.js';
6
+ let logger;
7
+ let controller;
8
+ let commands;
9
+ beforeEach(() => {
10
+ commands = [new FakeCommand(), new FakeCommand()];
11
+ logger = createMockInstance(Logger);
12
+ controller = new LoggerPadding({ logger });
13
+ });
14
+ it('returns same commands', () => {
15
+ expect(controller.handle(commands)).toMatchObject({ commands });
16
+ });
17
+ it('sets the prefix length on handle', () => {
18
+ controller.handle(commands);
19
+ expect(logger.setPrefixLength).toHaveBeenCalledTimes(1);
20
+ });
21
+ it('updates the prefix length when commands emit a start timer', () => {
22
+ controller.handle(commands);
23
+ commands[0].timer.next({ startDate: new Date() });
24
+ expect(logger.setPrefixLength).toHaveBeenCalledTimes(2);
25
+ commands[1].timer.next({ startDate: new Date() });
26
+ expect(logger.setPrefixLength).toHaveBeenCalledTimes(3);
27
+ });
28
+ it('sets prefix length to the longest prefix of all commands', () => {
29
+ logger.getPrefixContent
30
+ .mockReturnValueOnce({ type: 'default', value: 'foobar' })
31
+ .mockReturnValueOnce({ type: 'default', value: 'baz' });
32
+ controller.handle(commands);
33
+ expect(logger.setPrefixLength).toHaveBeenCalledWith(6);
34
+ });
35
+ it('ignores color markers when measuring prefix length', () => {
36
+ logger.getPrefixContent
37
+ .mockReturnValueOnce({ type: 'template', value: '{color}foo{/color}' })
38
+ .mockReturnValueOnce({ type: 'template', value: '{color}abcd{/color}' });
39
+ controller.handle(commands);
40
+ expect(logger.setPrefixLength).toHaveBeenCalledWith(4);
41
+ });
42
+ it('does not shorten the prefix length', () => {
43
+ logger.getPrefixContent
44
+ .mockReturnValueOnce({ type: 'default', value: '100' })
45
+ .mockReturnValueOnce({ type: 'default', value: '1' });
46
+ controller.handle(commands);
47
+ commands[0].timer.next({ startDate: new Date() });
48
+ expect(logger.setPrefixLength).toHaveBeenCalledWith(3);
49
+ commands[0].timer.next({ startDate: new Date() });
50
+ expect(logger.setPrefixLength).toHaveBeenCalledWith(3);
51
+ });
52
+ it('unsubscribes from start timers on finish', () => {
53
+ logger.getPrefixContent.mockReturnValue({ type: 'default', value: '1' });
54
+ const { onFinish } = controller.handle(commands);
55
+ commands[0].timer.next({ startDate: new Date() });
56
+ expect(logger.setPrefixLength).toHaveBeenCalledTimes(2);
57
+ onFinish();
58
+ commands[0].timer.next({ startDate: new Date() });
59
+ expect(logger.setPrefixLength).toHaveBeenCalledTimes(2);
60
+ });
@@ -1,8 +1,6 @@
1
- /// <reference types="node" />
2
- /// <reference types="node" />
3
- import { Writable } from 'stream';
4
- import { Command } from '../command';
5
- import { FlowController } from './flow-controller';
1
+ import { Writable } from 'node:stream';
2
+ import { Command } from '../command.js';
3
+ import { FlowController } from './flow-controller.js';
6
4
  /**
7
5
  * Kills processes and aborts further command spawning on output stream error (namely, SIGPIPE).
8
6
  */
@@ -15,6 +13,6 @@ export declare class OutputErrorHandler implements FlowController {
15
13
  });
16
14
  handle(commands: Command[]): {
17
15
  commands: Command[];
18
- onFinish(): void;
16
+ onFinish: () => void;
19
17
  };
20
18
  }
@@ -1,11 +1,8 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.OutputErrorHandler = void 0;
4
- const observables_1 = require("../observables");
1
+ import { fromSharedEvent } from '../observables.js';
5
2
  /**
6
3
  * Kills processes and aborts further command spawning on output stream error (namely, SIGPIPE).
7
4
  */
8
- class OutputErrorHandler {
5
+ export class OutputErrorHandler {
9
6
  outputStream;
10
7
  abortController;
11
8
  constructor({ abortController, outputStream, }) {
@@ -13,7 +10,7 @@ class OutputErrorHandler {
13
10
  this.outputStream = outputStream;
14
11
  }
15
12
  handle(commands) {
16
- const subscription = (0, observables_1.fromSharedEvent)(this.outputStream, 'error').subscribe(() => {
13
+ const subscription = fromSharedEvent(this.outputStream, 'error').subscribe(() => {
17
14
  commands.forEach((command) => command.kill());
18
15
  // Avoid further commands from spawning, e.g. if `RestartProcess` is used.
19
16
  this.abortController.abort();
@@ -24,4 +21,3 @@ class OutputErrorHandler {
24
21
  };
25
22
  }
26
23
  }
27
- exports.OutputErrorHandler = OutputErrorHandler;
@@ -0,0 +1,41 @@
1
+ import { Writable } from 'node:stream';
2
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
3
+ import { FakeCommand } from '../__fixtures__/fake-command.js';
4
+ import { OutputErrorHandler } from './output-error-handler.js';
5
+ let controller;
6
+ let outputStream;
7
+ let abortController;
8
+ let commands;
9
+ beforeEach(() => {
10
+ commands = [new FakeCommand(), new FakeCommand()];
11
+ abortController = new AbortController();
12
+ outputStream = new Writable();
13
+ controller = new OutputErrorHandler({ abortController, outputStream });
14
+ });
15
+ it('returns same commands', () => {
16
+ expect(controller.handle(commands)).toMatchObject({ commands });
17
+ });
18
+ describe('on output stream error', () => {
19
+ beforeEach(() => {
20
+ controller.handle(commands);
21
+ outputStream.emit('error', new Error('test'));
22
+ });
23
+ it('kills every command', () => {
24
+ expect(commands[0].kill).toHaveBeenCalled();
25
+ expect(commands[1].kill).toHaveBeenCalled();
26
+ });
27
+ it('sends abort signal', () => {
28
+ expect(abortController.signal.aborted).toBe(true);
29
+ });
30
+ });
31
+ describe('on finish', () => {
32
+ it('unsubscribes from output stream error', () => {
33
+ const { onFinish } = controller.handle(commands);
34
+ onFinish();
35
+ outputStream.on('error', vi.fn());
36
+ outputStream.emit('error', new Error('test'));
37
+ expect(commands[0].kill).not.toHaveBeenCalled();
38
+ expect(commands[1].kill).not.toHaveBeenCalled();
39
+ expect(abortController.signal.aborted).toBe(false);
40
+ });
41
+ });
@@ -1,7 +1,7 @@
1
- import * as Rx from 'rxjs';
2
- import { Command } from '../command';
3
- import { Logger } from '../logger';
4
- import { FlowController } from './flow-controller';
1
+ import Rx from 'rxjs';
2
+ import { Command } from '../command.js';
3
+ import { Logger } from '../logger.js';
4
+ import { FlowController } from './flow-controller.js';
5
5
  export type RestartDelay = number | 'exponential';
6
6
  /**
7
7
  * Restarts commands that fail up to a defined number of times.
@@ -1,36 +1,10 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
25
- Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.RestartProcess = void 0;
27
- const Rx = __importStar(require("rxjs"));
28
- const operators_1 = require("rxjs/operators");
29
- const defaults = __importStar(require("../defaults"));
1
+ import Rx from 'rxjs';
2
+ import { defaultIfEmpty, delayWhen, filter, map, skip, take, takeWhile } from 'rxjs/operators';
3
+ import * as defaults from '../defaults.js';
30
4
  /**
31
5
  * Restarts commands that fail up to a defined number of times.
32
6
  */
33
- class RestartProcess {
7
+ export class RestartProcess {
34
8
  logger;
35
9
  scheduler;
36
10
  delay;
@@ -46,21 +20,21 @@ class RestartProcess {
46
20
  if (this.tries === 0) {
47
21
  return { commands };
48
22
  }
49
- const delayOperator = (0, operators_1.delayWhen)((_, index) => {
23
+ const delayOperator = delayWhen((_, index) => {
50
24
  const { delay } = this;
51
- const value = delay === 'exponential' ? Math.pow(2, index) * 1000 : delay;
25
+ const value = delay === 'exponential' ? 2 ** index * 1000 : delay;
52
26
  return Rx.timer(value, this.scheduler);
53
27
  });
54
28
  commands
55
- .map((command) => command.close.pipe((0, operators_1.take)(this.tries), (0, operators_1.takeWhile)(({ exitCode }) => exitCode !== 0)))
29
+ .map((command) => command.close.pipe(take(this.tries), takeWhile(({ exitCode }) => exitCode !== 0)))
56
30
  .forEach((failure, index) => Rx.merge(
57
31
  // Delay the emission (so that the restarts happen on time),
58
32
  // explicitly telling the subscriber that a restart is needed
59
- failure.pipe(delayOperator, (0, operators_1.map)(() => true)),
33
+ failure.pipe(delayOperator, map(() => true)),
60
34
  // Skip the first N emissions (as these would be duplicates of the above),
61
35
  // meaning it will be empty because of success, or failed all N times,
62
36
  // and no more restarts should be attempted.
63
- failure.pipe((0, operators_1.skip)(this.tries), (0, operators_1.map)(() => false), (0, operators_1.defaultIfEmpty)(false))).subscribe((restart) => {
37
+ failure.pipe(skip(this.tries), map(() => false), defaultIfEmpty(false))).subscribe((restart) => {
64
38
  const command = commands[index];
65
39
  if (restart) {
66
40
  this.logger.logCommandEvent(`${command.command} restarted`, command);
@@ -69,7 +43,7 @@ class RestartProcess {
69
43
  }));
70
44
  return {
71
45
  commands: commands.map((command) => {
72
- const closeStream = command.close.pipe((0, operators_1.filter)(({ exitCode }, emission) => {
46
+ const closeStream = command.close.pipe(filter(({ exitCode }, emission) => {
73
47
  // We let all success codes pass, and failures only after restarting won't happen again
74
48
  return exitCode === 0 || emission >= this.tries;
75
49
  }));
@@ -85,4 +59,3 @@ class RestartProcess {
85
59
  };
86
60
  }
87
61
  }
88
- exports.RestartProcess = RestartProcess;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,127 @@
1
+ import { VirtualTimeScheduler } from 'rxjs';
2
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
3
+ import { createMockInstance } from '../__fixtures__/create-mock-instance.js';
4
+ import { createFakeCloseEvent, FakeCommand } from '../__fixtures__/fake-command.js';
5
+ import { Logger } from '../logger.js';
6
+ import { RestartProcess } from './restart-process.js';
7
+ let commands;
8
+ let controller;
9
+ let logger;
10
+ let scheduler;
11
+ beforeEach(() => {
12
+ commands = [new FakeCommand(), new FakeCommand()];
13
+ logger = createMockInstance(Logger);
14
+ // Don't use TestScheduler as it's hardcoded to a max number of "frames" (time),
15
+ // which don't work for some tests in this suite
16
+ scheduler = new VirtualTimeScheduler();
17
+ controller = new RestartProcess({
18
+ logger,
19
+ scheduler,
20
+ delay: 100,
21
+ tries: 2,
22
+ });
23
+ });
24
+ it('does not restart processes that complete with success', () => {
25
+ controller.handle(commands);
26
+ commands[0].close.next(createFakeCloseEvent({ exitCode: 0 }));
27
+ commands[1].close.next(createFakeCloseEvent({ exitCode: 0 }));
28
+ scheduler.flush();
29
+ expect(commands[0].start).toHaveBeenCalledTimes(0);
30
+ expect(commands[1].start).toHaveBeenCalledTimes(0);
31
+ });
32
+ it('restarts processes that fail immediately, if no delay was passed', () => {
33
+ controller = new RestartProcess({ logger, scheduler, tries: 1 });
34
+ controller.handle(commands);
35
+ commands[0].close.next(createFakeCloseEvent({ exitCode: 1 }));
36
+ scheduler.flush();
37
+ expect(scheduler.now()).toBe(0);
38
+ expect(logger.logCommandEvent).toHaveBeenCalledExactlyOnceWith(`${commands[0].command} restarted`, commands[0]);
39
+ expect(commands[0].start).toHaveBeenCalledTimes(1);
40
+ });
41
+ it('restarts processes that fail after delay ms has passed', () => {
42
+ controller.handle(commands);
43
+ commands[0].close.next(createFakeCloseEvent({ exitCode: 1 }));
44
+ commands[1].close.next(createFakeCloseEvent({ exitCode: 0 }));
45
+ scheduler.flush();
46
+ expect(scheduler.now()).toBe(100);
47
+ expect(logger.logCommandEvent).toHaveBeenCalledExactlyOnceWith(`${commands[0].command} restarted`, commands[0]);
48
+ expect(commands[0].start).toHaveBeenCalledTimes(1);
49
+ expect(commands[1].start).not.toHaveBeenCalled();
50
+ });
51
+ it('restarts processes that fail with an exponential back-off', () => {
52
+ const tries = 4;
53
+ controller = new RestartProcess({ logger, scheduler, tries, delay: 'exponential' });
54
+ controller.handle(commands);
55
+ let time = 0;
56
+ for (let i = 0; i < tries; i++) {
57
+ commands[0].close.next(createFakeCloseEvent({ exitCode: 1 }));
58
+ scheduler.flush();
59
+ time += 2 ** i * 1000;
60
+ expect(scheduler.now()).toBe(time);
61
+ expect(logger.logCommandEvent).toHaveBeenCalledTimes(i + 1);
62
+ expect(commands[0].start).toHaveBeenCalledTimes(i + 1);
63
+ }
64
+ });
65
+ it('restarts processes up to tries', () => {
66
+ controller.handle(commands);
67
+ commands[0].close.next(createFakeCloseEvent({ exitCode: 1 }));
68
+ commands[0].close.next(createFakeCloseEvent({ exitCode: 'SIGTERM' }));
69
+ commands[0].close.next(createFakeCloseEvent({ exitCode: 'SIGTERM' }));
70
+ commands[1].close.next(createFakeCloseEvent({ exitCode: 0 }));
71
+ scheduler.flush();
72
+ expect(logger.logCommandEvent).toHaveBeenCalledTimes(2);
73
+ expect(logger.logCommandEvent).toHaveBeenCalledWith(`${commands[0].command} restarted`, commands[0]);
74
+ expect(commands[0].start).toHaveBeenCalledTimes(2);
75
+ });
76
+ it('restart processes forever, if tries is negative', () => {
77
+ controller = new RestartProcess({
78
+ logger,
79
+ scheduler,
80
+ delay: 100,
81
+ tries: -1,
82
+ });
83
+ expect(controller.tries).toBe(Infinity);
84
+ });
85
+ it('restarts processes until they succeed', () => {
86
+ controller.handle(commands);
87
+ commands[0].close.next(createFakeCloseEvent({ exitCode: 1 }));
88
+ commands[0].close.next(createFakeCloseEvent({ exitCode: 0 }));
89
+ commands[1].close.next(createFakeCloseEvent({ exitCode: 0 }));
90
+ scheduler.flush();
91
+ expect(logger.logCommandEvent).toHaveBeenCalledExactlyOnceWith(`${commands[0].command} restarted`, commands[0]);
92
+ expect(commands[0].start).toHaveBeenCalledTimes(1);
93
+ });
94
+ describe('returned commands', () => {
95
+ it('are the same if 0 tries are to be attempted', () => {
96
+ controller = new RestartProcess({ logger, scheduler });
97
+ expect(controller.handle(commands)).toMatchObject({ commands });
98
+ });
99
+ it('are not the same, but with same length if 1+ tries are to be attempted', () => {
100
+ const { commands: newCommands } = controller.handle(commands);
101
+ expect(newCommands).not.toBe(commands);
102
+ expect(newCommands).toHaveLength(commands.length);
103
+ });
104
+ it('skip close events followed by restarts', () => {
105
+ const { commands: newCommands } = controller.handle(commands);
106
+ const callback = vi.fn();
107
+ newCommands[0].close.subscribe(callback);
108
+ newCommands[1].close.subscribe(callback);
109
+ commands[0].close.next(createFakeCloseEvent({ exitCode: 1 }));
110
+ commands[0].close.next(createFakeCloseEvent({ exitCode: 1 }));
111
+ commands[0].close.next(createFakeCloseEvent({ exitCode: 1 }));
112
+ commands[1].close.next(createFakeCloseEvent({ exitCode: 1 }));
113
+ commands[1].close.next(createFakeCloseEvent({ exitCode: 0 }));
114
+ scheduler.flush();
115
+ // 1 failure from commands[0], 1 success from commands[1]
116
+ expect(callback).toHaveBeenCalledTimes(2);
117
+ });
118
+ it('keep non-close streams from original commands', () => {
119
+ const { commands: newCommands } = controller.handle(commands);
120
+ newCommands.forEach((newCommand, i) => {
121
+ expect(newCommand.close).not.toBe(commands[i].close);
122
+ expect(newCommand.error).toBe(commands[i].error);
123
+ expect(newCommand.stdout).toBe(commands[i].stdout);
124
+ expect(newCommand.stderr).toBe(commands[i].stderr);
125
+ });
126
+ });
127
+ });
@@ -1,6 +1,6 @@
1
- import { Command, SpawnCommand } from '../command';
2
- import { Logger } from '../logger';
3
- import { FlowController } from './flow-controller';
1
+ import { Command, SpawnCommand } from '../command.js';
2
+ import { Logger } from '../logger.js';
3
+ import { FlowController } from './flow-controller.js';
4
4
  export declare class Teardown implements FlowController {
5
5
  private readonly logger;
6
6
  private readonly spawn;
@@ -9,9 +9,8 @@ export declare class Teardown implements FlowController {
9
9
  logger: Logger;
10
10
  /**
11
11
  * Which function to use to spawn commands.
12
- * Defaults to the same used by the rest of concurrently.
13
12
  */
14
- spawn?: SpawnCommand;
13
+ spawn: SpawnCommand;
15
14
  commands: readonly string[];
16
15
  });
17
16
  handle(commands: Command[]): {