concurrently 8.2.2 → 9.0.1

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 (62) hide show
  1. package/README.md +34 -239
  2. package/dist/bin/concurrently.js +37 -22
  3. package/dist/bin/read-package.d.ts +6 -0
  4. package/dist/bin/read-package.js +47 -0
  5. package/dist/src/command-parser/expand-arguments.d.ts +1 -0
  6. package/dist/src/command-parser/expand-arguments.js +1 -0
  7. package/dist/src/command-parser/expand-npm-shortcut.d.ts +1 -1
  8. package/dist/src/command-parser/expand-npm-shortcut.js +4 -3
  9. package/dist/src/command-parser/expand-npm-wildcard.js +4 -2
  10. package/dist/src/command-parser/strip-quotes.d.ts +1 -0
  11. package/dist/src/command.d.ts +46 -5
  12. package/dist/src/command.js +91 -16
  13. package/dist/src/completion-listener.d.ts +4 -1
  14. package/dist/src/completion-listener.js +30 -6
  15. package/dist/src/concurrently.d.ts +16 -4
  16. package/dist/src/concurrently.js +15 -14
  17. package/dist/src/date-format.d.ts +19 -0
  18. package/dist/src/date-format.js +318 -0
  19. package/dist/src/defaults.d.ts +1 -1
  20. package/dist/src/defaults.js +1 -1
  21. package/dist/src/flow-control/flow-controller.d.ts +1 -1
  22. package/dist/src/flow-control/input-handler.js +4 -0
  23. package/dist/src/flow-control/kill-on-signal.d.ts +4 -1
  24. package/dist/src/flow-control/kill-on-signal.js +8 -1
  25. package/dist/src/flow-control/kill-others.d.ts +4 -1
  26. package/dist/src/flow-control/kill-others.js +7 -1
  27. package/dist/src/flow-control/log-error.js +1 -0
  28. package/dist/src/flow-control/log-exit.js +1 -0
  29. package/dist/src/flow-control/log-output.js +1 -0
  30. package/dist/src/flow-control/log-timings.d.ts +1 -1
  31. package/dist/src/flow-control/log-timings.js +6 -4
  32. package/dist/src/flow-control/logger-padding.d.ts +13 -0
  33. package/dist/src/flow-control/logger-padding.js +35 -0
  34. package/dist/src/flow-control/restart-process.d.ts +3 -2
  35. package/dist/src/flow-control/restart-process.js +14 -2
  36. package/dist/src/flow-control/teardown.d.ts +21 -0
  37. package/dist/src/flow-control/teardown.js +72 -0
  38. package/dist/src/index.d.ts +18 -8
  39. package/dist/src/index.js +28 -7
  40. package/dist/src/logger.d.ts +25 -10
  41. package/dist/src/logger.js +78 -39
  42. package/dist/src/output-writer.js +6 -2
  43. package/dist/src/prefix-color-selector.js +1 -0
  44. package/dist/src/{get-spawn-opts.d.ts → spawn.d.ts} +20 -5
  45. package/dist/src/spawn.js +49 -0
  46. package/docs/README.md +13 -0
  47. package/docs/cli/configuration.md +11 -0
  48. package/docs/cli/input-handling.md +40 -0
  49. package/docs/cli/output-control.md +35 -0
  50. package/docs/cli/passthrough-arguments.md +80 -0
  51. package/docs/cli/prefixing.md +147 -0
  52. package/docs/cli/restarting.md +38 -0
  53. package/docs/cli/shortcuts.md +72 -0
  54. package/docs/demo.gif +0 -0
  55. package/index.d.mts +7 -0
  56. package/index.d.ts +11 -0
  57. package/index.js +6 -1
  58. package/index.mjs +2 -2
  59. package/package.json +37 -29
  60. package/dist/bin/epilogue.d.ts +0 -1
  61. package/dist/bin/epilogue.js +0 -90
  62. package/dist/src/get-spawn-opts.js +0 -18
@@ -0,0 +1,318 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DateFormatter = void 0;
4
+ /**
5
+ * Unicode-compliant date/time formatter.
6
+ *
7
+ * @see https://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns
8
+ */
9
+ class DateFormatter {
10
+ options;
11
+ static tokenRegex = /[A-Z]/i;
12
+ parts = [];
13
+ constructor(pattern, options = {}) {
14
+ this.options = options;
15
+ let i = 0;
16
+ while (i < pattern.length) {
17
+ const char = pattern[i];
18
+ const { fn, length } = char === "'"
19
+ ? this.compileLiteral(pattern, i)
20
+ : DateFormatter.tokenRegex.test(char)
21
+ ? this.compileToken(pattern, i)
22
+ : this.compileOther(pattern, i);
23
+ this.parts.push(fn);
24
+ i += length;
25
+ }
26
+ }
27
+ compileLiteral(pattern, offset) {
28
+ let length = 1;
29
+ let value = '';
30
+ for (; length < pattern.length; length++) {
31
+ const i = offset + length;
32
+ const char = pattern[i];
33
+ if (char === "'") {
34
+ const nextChar = pattern[i + 1];
35
+ length++;
36
+ // if the next character is another single quote, it's been escaped.
37
+ // if not, then the literal has been closed
38
+ if (nextChar !== "'") {
39
+ break;
40
+ }
41
+ }
42
+ value += char;
43
+ }
44
+ return { fn: () => value || "'", length };
45
+ }
46
+ compileOther(pattern, offset) {
47
+ let value = '';
48
+ while (!DateFormatter.tokenRegex.test(pattern[offset]) && pattern[offset] !== "'") {
49
+ value += pattern[offset++];
50
+ }
51
+ return { fn: () => value, length: value.length };
52
+ }
53
+ compileToken(pattern, offset) {
54
+ const type = pattern[offset];
55
+ const token = tokens.get(type);
56
+ if (!token) {
57
+ throw new SyntaxError(`Formatting token "${type}" is invalid`);
58
+ }
59
+ let length = 0;
60
+ while (pattern[offset + length] === type) {
61
+ length++;
62
+ }
63
+ const tokenFn = token[length - 1];
64
+ if (!tokenFn) {
65
+ throw new RangeError(`Formatting token "${type.repeat(length)}" is unsupported`);
66
+ }
67
+ return { fn: tokenFn, length };
68
+ }
69
+ format(date) {
70
+ return this.parts.reduce((output, part) => output + String(part(date, this.options)), '');
71
+ }
72
+ }
73
+ exports.DateFormatter = DateFormatter;
74
+ /**
75
+ * A map of token to its implementations by length.
76
+ * If an index is undefined, then that token length is unsupported.
77
+ */
78
+ const tokens = new Map()
79
+ // era
80
+ .set('G', [
81
+ makeTokenFn({ era: 'short' }, 'era'),
82
+ makeTokenFn({ era: 'short' }, 'era'),
83
+ makeTokenFn({ era: 'short' }, 'era'),
84
+ makeTokenFn({ era: 'long' }, 'era'),
85
+ makeTokenFn({ era: 'narrow' }, 'era'),
86
+ ])
87
+ // year
88
+ .set('y', [
89
+ // TODO: does not support BC years.
90
+ // https://stackoverflow.com/a/41345095/2083599
91
+ (date) => date.getFullYear(),
92
+ (date) => pad(2, date.getFullYear()).slice(-2),
93
+ (date) => pad(3, date.getFullYear()),
94
+ (date) => pad(4, date.getFullYear()),
95
+ (date) => pad(5, date.getFullYear()),
96
+ ])
97
+ .set('Y', [
98
+ getWeekYear,
99
+ (date, options) => pad(2, getWeekYear(date, options)).slice(-2),
100
+ (date, options) => pad(3, getWeekYear(date, options)),
101
+ (date, options) => pad(4, getWeekYear(date, options)),
102
+ (date, options) => pad(5, getWeekYear(date, options)),
103
+ ])
104
+ .set('u', [])
105
+ .set('U', [
106
+ // Fallback implemented as yearName is not available in gregorian calendars, for instance.
107
+ makeTokenFn({ dateStyle: 'full' }, 'yearName', (date) => String(date.getFullYear())),
108
+ ])
109
+ .set('r', [
110
+ // Fallback implemented as relatedYear is not available in gregorian calendars, for instance.
111
+ makeTokenFn({ dateStyle: 'full' }, 'relatedYear', (date) => String(date.getFullYear())),
112
+ ])
113
+ // quarter
114
+ .set('Q', [
115
+ (date) => Math.floor(date.getMonth() / 3) + 1,
116
+ (date) => pad(2, Math.floor(date.getMonth() / 3) + 1),
117
+ // these aren't localized in Intl.DateTimeFormat.
118
+ undefined,
119
+ undefined,
120
+ (date) => Math.floor(date.getMonth() / 3) + 1,
121
+ ])
122
+ .set('q', [
123
+ (date) => Math.floor(date.getMonth() / 3) + 1,
124
+ (date) => pad(2, Math.floor(date.getMonth() / 3) + 1),
125
+ // these aren't localized in Intl.DateTimeFormat.
126
+ undefined,
127
+ undefined,
128
+ (date) => Math.floor(date.getMonth() / 3) + 1,
129
+ ])
130
+ // month
131
+ .set('M', [
132
+ (date) => date.getMonth() + 1,
133
+ (date) => pad(2, date.getMonth() + 1),
134
+ // these include the day so that it forces non-stand-alone month part
135
+ makeTokenFn({ day: 'numeric', month: 'short' }, 'month'),
136
+ makeTokenFn({ day: 'numeric', month: 'long' }, 'month'),
137
+ makeTokenFn({ day: 'numeric', month: 'narrow' }, 'month'),
138
+ ])
139
+ .set('L', [
140
+ (date) => date.getMonth() + 1,
141
+ (date) => pad(2, date.getMonth() + 1),
142
+ makeTokenFn({ month: 'short' }, 'month'),
143
+ makeTokenFn({ month: 'long' }, 'month'),
144
+ makeTokenFn({ month: 'narrow' }, 'month'),
145
+ ])
146
+ .set('l', [() => ''])
147
+ // week
148
+ .set('w', [getWeek, (date, options) => pad(2, getWeek(date, options))])
149
+ .set('W', [getWeekOfMonth])
150
+ // day
151
+ .set('d', [(date) => date.getDate(), (date) => pad(2, date.getDate())])
152
+ .set('D', [
153
+ getDayOfYear,
154
+ (date) => pad(2, getDayOfYear(date)),
155
+ (date) => pad(3, getDayOfYear(date)),
156
+ ])
157
+ .set('F', [(date) => Math.ceil(date.getDate() / 7)])
158
+ .set('g', [])
159
+ // week day
160
+ .set('E', [
161
+ makeTokenFn({ weekday: 'short' }, 'weekday'),
162
+ makeTokenFn({ weekday: 'short' }, 'weekday'),
163
+ makeTokenFn({ weekday: 'short' }, 'weekday'),
164
+ makeTokenFn({ weekday: 'long' }, 'weekday'),
165
+ ])
166
+ .set('e', [
167
+ undefined,
168
+ undefined,
169
+ makeTokenFn({ weekday: 'short' }, 'weekday'),
170
+ makeTokenFn({ weekday: 'long' }, 'weekday'),
171
+ ])
172
+ .set('c', [])
173
+ // period
174
+ .set('a', [
175
+ makeTokenFn({ hour12: true, timeStyle: 'full' }, 'dayPeriod'),
176
+ makeTokenFn({ hour12: true, timeStyle: 'full' }, 'dayPeriod'),
177
+ makeTokenFn({ hour12: true, timeStyle: 'full' }, 'dayPeriod'),
178
+ ])
179
+ .set('b', [])
180
+ .set('B', [
181
+ makeTokenFn({ dayPeriod: 'short' }, 'dayPeriod'),
182
+ makeTokenFn({ dayPeriod: 'short' }, 'dayPeriod'),
183
+ makeTokenFn({ dayPeriod: 'short' }, 'dayPeriod'),
184
+ makeTokenFn({ dayPeriod: 'long' }, 'dayPeriod'),
185
+ ])
186
+ // hour
187
+ .set('h', [(date) => date.getHours() % 12 || 12, (date) => pad(2, date.getHours() % 12 || 12)])
188
+ .set('H', [(date) => date.getHours(), (date) => pad(2, date.getHours())])
189
+ .set('K', [(date) => date.getHours() % 12, (date) => pad(2, date.getHours() % 12)])
190
+ .set('k', [(date) => date.getHours() % 24 || 24, (date) => pad(2, date.getHours() % 24 || 24)])
191
+ .set('j', [])
192
+ .set('J', [])
193
+ .set('C', [])
194
+ // minute
195
+ .set('m', [(date) => date.getMinutes(), (date) => pad(2, date.getMinutes())])
196
+ // second
197
+ .set('s', [(date) => date.getSeconds(), (date) => pad(2, date.getSeconds())])
198
+ .set('S', [
199
+ (date) => Math.trunc(date.getMilliseconds() / 100),
200
+ (date) => pad(2, Math.trunc(date.getMilliseconds() / 10)),
201
+ (date) => pad(3, Math.trunc(date.getMilliseconds())),
202
+ ])
203
+ .set('A', [])
204
+ // zone
205
+ // none of these have tests
206
+ .set('z', [
207
+ makeTokenFn({ timeZoneName: 'short' }, 'timeZoneName'),
208
+ makeTokenFn({ timeZoneName: 'short' }, 'timeZoneName'),
209
+ makeTokenFn({ timeZoneName: 'short' }, 'timeZoneName'),
210
+ makeTokenFn({ timeZoneName: 'long' }, 'timeZoneName'),
211
+ ])
212
+ .set('Z', [
213
+ undefined,
214
+ undefined,
215
+ undefined,
216
+ // equivalent to `OOOO`.
217
+ makeTokenFn({ timeZoneName: 'longOffset' }, 'timeZoneName'),
218
+ ])
219
+ .set('O', [
220
+ makeTokenFn({ timeZoneName: 'shortOffset' }, 'timeZoneName'),
221
+ undefined,
222
+ undefined,
223
+ // equivalent to `ZZZZ`.
224
+ makeTokenFn({ timeZoneName: 'longOffset' }, 'timeZoneName'),
225
+ ])
226
+ .set('v', [
227
+ makeTokenFn({ timeZoneName: 'shortGeneric' }, 'timeZoneName'),
228
+ undefined,
229
+ undefined,
230
+ makeTokenFn({ timeZoneName: 'longGeneric' }, 'timeZoneName'),
231
+ ])
232
+ .set('V', [])
233
+ .set('X', [])
234
+ .set('x', []);
235
+ let locale;
236
+ function getLocale(options) {
237
+ if (!locale || locale.baseName !== options.locale) {
238
+ locale = new Intl.Locale(options.locale || new Intl.DateTimeFormat().resolvedOptions().locale);
239
+ }
240
+ return locale;
241
+ }
242
+ /**
243
+ * Creates a token formatting function that returns the value of the chosen part type,
244
+ * using the current locale's settings.
245
+ *
246
+ * If the date/formatter settings doesn't include the requested part type,
247
+ * the `fallback` function is invoked, if specified. If none has been specified, returns an
248
+ * empty string.
249
+ */
250
+ function makeTokenFn(options, type, fallback) {
251
+ let formatter;
252
+ return (date, formatterOptions) => {
253
+ // Allow tests to set a different locale and have that cause the formatter to be recreated
254
+ if (!formatter ||
255
+ formatter.resolvedOptions().locale !== formatterOptions.locale ||
256
+ formatter.resolvedOptions().calendar !== formatterOptions.calendar) {
257
+ formatter = new Intl.DateTimeFormat(formatterOptions.locale, {
258
+ ...options,
259
+ calendar: options.calendar ?? formatterOptions.calendar,
260
+ });
261
+ }
262
+ const parts = formatter.formatToParts(date);
263
+ const part = parts.find((p) => p.type === type);
264
+ return part?.value ?? (fallback ? fallback(date, formatterOptions) : '');
265
+ };
266
+ }
267
+ function startOfWeek(date, options) {
268
+ const locale = getLocale(options);
269
+ const firstDay = locale.weekInfo.firstDay === 7 ? 0 : locale.weekInfo.firstDay;
270
+ const day = date.getDay();
271
+ const diff = (day < firstDay ? 7 : 0) + day - firstDay;
272
+ date.setDate(date.getDate() - diff);
273
+ date.setHours(0, 0, 0, 0);
274
+ return date;
275
+ }
276
+ function getWeekYear(date, options) {
277
+ const locale = getLocale(options);
278
+ const minimalDays = locale.weekInfo.minimalDays;
279
+ const year = date.getFullYear();
280
+ const thisYear = startOfWeek(new Date(year, 0, minimalDays), options);
281
+ const nextYear = startOfWeek(new Date(year + 1, 0, minimalDays), options);
282
+ if (date.getTime() >= nextYear.getTime()) {
283
+ return year + 1;
284
+ }
285
+ else if (date.getTime() >= thisYear.getTime()) {
286
+ return year;
287
+ }
288
+ else {
289
+ return year - 1;
290
+ }
291
+ }
292
+ function getWeek(date, options) {
293
+ const locale = getLocale(options);
294
+ const weekMs = 7 * 24 * 3600 * 1000;
295
+ const temp = startOfWeek(new Date(date), options);
296
+ const thisYear = new Date(getWeekYear(date, options), 0, locale.weekInfo.minimalDays);
297
+ startOfWeek(thisYear, options);
298
+ const diff = temp.getTime() - thisYear.getTime();
299
+ return Math.round(diff / weekMs) + 1;
300
+ }
301
+ function getWeekOfMonth(date, options) {
302
+ const current = new Date(date);
303
+ current.setHours(0, 0, 0, 0);
304
+ const monthWeekStart = startOfWeek(new Date(date.getFullYear(), date.getMonth(), 1), options);
305
+ const weekMs = 7 * 24 * 3600 * 1000;
306
+ return Math.floor((date.getTime() - monthWeekStart.getTime()) / weekMs) + 1;
307
+ }
308
+ function getDayOfYear(date) {
309
+ let days = 0;
310
+ for (let i = 0; i <= date.getMonth() - 1; i++) {
311
+ const temp = new Date(date.getFullYear(), i + 1, 0, 0, 0, 0);
312
+ days += temp.getDate();
313
+ }
314
+ return days + date.getDate();
315
+ }
316
+ function pad(length, val) {
317
+ return String(val).padStart(length, '0');
318
+ }
@@ -44,7 +44,7 @@ export declare const restartDelay = 0;
44
44
  export declare const success: SuccessCondition;
45
45
  /**
46
46
  * Date format used when logging date/time.
47
- * @see https://date-fns.org/v2.0.1/docs/format
47
+ * @see https://www.unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
48
48
  */
49
49
  export declare const timestampFormat = "yyyy-MM-dd HH:mm:ss.SSS";
50
50
  /**
@@ -49,7 +49,7 @@ exports.restartDelay = 0;
49
49
  exports.success = 'all';
50
50
  /**
51
51
  * Date format used when logging date/time.
52
- * @see https://date-fns.org/v2.0.1/docs/format
52
+ * @see https://www.unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
53
53
  */
54
54
  exports.timestampFormat = 'yyyy-MM-dd HH:mm:ss.SSS';
55
55
  /**
@@ -8,6 +8,6 @@ import { Command } from '../command';
8
8
  export interface FlowController {
9
9
  handle(commands: Command[]): {
10
10
  commands: Command[];
11
- onFinish?: () => void;
11
+ onFinish?: () => void | Promise<void>;
12
12
  };
13
13
  }
@@ -37,6 +37,10 @@ const defaults = __importStar(require("../defaults"));
37
37
  * If the input doesn't start with a command identifier, it is then always sent to the default target.
38
38
  */
39
39
  class InputHandler {
40
+ logger;
41
+ defaultInputTarget;
42
+ inputStream;
43
+ pauseInputStreamOnFinish;
40
44
  constructor({ defaultInputTarget, inputStream, pauseInputStreamOnFinish, logger, }) {
41
45
  this.logger = logger;
42
46
  this.defaultInputTarget = defaultInputTarget || defaults.defaultInputTarget;
@@ -1,4 +1,5 @@
1
1
  /// <reference types="node" />
2
+ /// <reference types="node" />
2
3
  import EventEmitter from 'events';
3
4
  import { Command } from '../command';
4
5
  import { FlowController } from './flow-controller';
@@ -8,8 +9,10 @@ import { FlowController } from './flow-controller';
8
9
  */
9
10
  export declare class KillOnSignal implements FlowController {
10
11
  private readonly process;
11
- constructor({ process }: {
12
+ private readonly abortController?;
13
+ constructor({ process, abortController, }: {
12
14
  process: EventEmitter;
15
+ abortController?: AbortController;
13
16
  });
14
17
  handle(commands: Command[]): {
15
18
  commands: Command[];
@@ -7,14 +7,18 @@ const operators_1 = require("rxjs/operators");
7
7
  * command.
8
8
  */
9
9
  class KillOnSignal {
10
- constructor({ process }) {
10
+ process;
11
+ abortController;
12
+ constructor({ process, abortController, }) {
11
13
  this.process = process;
14
+ this.abortController = abortController;
12
15
  }
13
16
  handle(commands) {
14
17
  let caughtSignal;
15
18
  ['SIGINT', 'SIGTERM', 'SIGHUP'].forEach((signal) => {
16
19
  this.process.on(signal, () => {
17
20
  caughtSignal = signal;
21
+ this.abortController?.abort();
18
22
  commands.forEach((command) => command.kill(signal));
19
23
  });
20
24
  });
@@ -24,6 +28,9 @@ class KillOnSignal {
24
28
  const exitCode = caughtSignal === 'SIGINT' ? 0 : exitInfo.exitCode;
25
29
  return { ...exitInfo, exitCode };
26
30
  }));
31
+ // Return a proxy so that mutations happen on the original Command object.
32
+ // If either `Object.assign()` or `Object.create()` were used, it'd be hard to
33
+ // reflect the mutations on Command objects referenced by previous flow controllers.
27
34
  return new Proxy(command, {
28
35
  get(target, prop) {
29
36
  return prop === 'close' ? closeStream : target[prop];
@@ -1,3 +1,4 @@
1
+ /// <reference types="node" />
1
2
  import { Command } from '../command';
2
3
  import { Logger } from '../logger';
3
4
  import { FlowController } from './flow-controller';
@@ -7,10 +8,12 @@ export type ProcessCloseCondition = 'failure' | 'success';
7
8
  */
8
9
  export declare class KillOthers implements FlowController {
9
10
  private readonly logger;
11
+ private readonly abortController?;
10
12
  private readonly conditions;
11
13
  private readonly killSignal;
12
- constructor({ logger, conditions, killSignal, }: {
14
+ constructor({ logger, abortController, conditions, killSignal, }: {
13
15
  logger: Logger;
16
+ abortController?: AbortController;
14
17
  conditions: ProcessCloseCondition | ProcessCloseCondition[];
15
18
  killSignal: string | undefined;
16
19
  });
@@ -11,8 +11,13 @@ const command_1 = require("../command");
11
11
  * Sends a SIGTERM signal to all commands when one of the commands exits with a matching condition.
12
12
  */
13
13
  class KillOthers {
14
- constructor({ logger, conditions, killSignal, }) {
14
+ logger;
15
+ abortController;
16
+ conditions;
17
+ killSignal;
18
+ constructor({ logger, abortController, conditions, killSignal, }) {
15
19
  this.logger = logger;
20
+ this.abortController = abortController;
16
21
  this.conditions = lodash_1.default.castArray(conditions);
17
22
  this.killSignal = killSignal;
18
23
  }
@@ -23,6 +28,7 @@ class KillOthers {
23
28
  }
24
29
  const closeStates = commands.map((command) => command.close.pipe((0, operators_1.map)(({ exitCode }) => exitCode === 0 ? 'success' : 'failure'), (0, operators_1.filter)((state) => conditions.includes(state))));
25
30
  closeStates.forEach((closeState) => closeState.subscribe(() => {
31
+ this.abortController?.abort();
26
32
  const killableCommands = commands.filter((command) => command_1.Command.canKill(command));
27
33
  if (killableCommands.length) {
28
34
  this.logger.logGlobalEvent(`Sending ${this.killSignal || 'SIGTERM'} to other processes..`);
@@ -5,6 +5,7 @@ exports.LogError = void 0;
5
5
  * Logs when commands failed executing, e.g. due to the executable not existing in the system.
6
6
  */
7
7
  class LogError {
8
+ logger;
8
9
  constructor({ logger }) {
9
10
  this.logger = logger;
10
11
  }
@@ -5,6 +5,7 @@ exports.LogExit = void 0;
5
5
  * Logs the exit code/signal of commands.
6
6
  */
7
7
  class LogExit {
8
+ logger;
8
9
  constructor({ logger }) {
9
10
  this.logger = logger;
10
11
  }
@@ -5,6 +5,7 @@ exports.LogOutput = void 0;
5
5
  * Logs the stdout and stderr output of commands.
6
6
  */
7
7
  class LogOutput {
8
+ logger;
8
9
  constructor({ logger }) {
9
10
  this.logger = logger;
10
11
  }
@@ -14,7 +14,7 @@ type TimingInfo = {
14
14
  export declare class LogTimings implements FlowController {
15
15
  static mapCloseEventToTimingInfo({ command, timings, killed, exitCode, }: CloseEvent): TimingInfo;
16
16
  private readonly logger?;
17
- private readonly timestampFormat;
17
+ private readonly dateFormatter;
18
18
  constructor({ logger, timestampFormat, }: {
19
19
  logger?: Logger;
20
20
  timestampFormat?: string;
@@ -28,10 +28,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
29
  exports.LogTimings = void 0;
30
30
  const assert = __importStar(require("assert"));
31
- const format_1 = __importDefault(require("date-fns/format"));
32
31
  const lodash_1 = __importDefault(require("lodash"));
33
32
  const Rx = __importStar(require("rxjs"));
34
33
  const operators_1 = require("rxjs/operators");
34
+ const date_format_1 = require("../date-format");
35
35
  const defaults = __importStar(require("../defaults"));
36
36
  /**
37
37
  * Logs timing information about commands as they start/stop and then a summary when all commands finish.
@@ -47,9 +47,11 @@ class LogTimings {
47
47
  command: command.command,
48
48
  };
49
49
  }
50
+ logger;
51
+ dateFormatter;
50
52
  constructor({ logger, timestampFormat = defaults.timestampFormat, }) {
51
53
  this.logger = logger;
52
- this.timestampFormat = timestampFormat;
54
+ this.dateFormatter = new date_format_1.DateFormatter(timestampFormat);
53
55
  }
54
56
  printExitInfoTimingTable(exitInfos) {
55
57
  assert.ok(this.logger);
@@ -71,12 +73,12 @@ class LogTimings {
71
73
  commands.forEach((command) => {
72
74
  command.timer.subscribe(({ startDate, endDate }) => {
73
75
  if (!endDate) {
74
- const formattedStartDate = (0, format_1.default)(startDate, this.timestampFormat);
76
+ const formattedStartDate = this.dateFormatter.format(startDate);
75
77
  logger.logCommandEvent(`${command.command} started at ${formattedStartDate}`, command);
76
78
  }
77
79
  else {
78
80
  const durationMs = endDate.getTime() - startDate.getTime();
79
- const formattedEndDate = (0, format_1.default)(endDate, this.timestampFormat);
81
+ const formattedEndDate = this.dateFormatter.format(endDate);
80
82
  logger.logCommandEvent(`${command.command} stopped at ${formattedEndDate} after ${durationMs.toLocaleString()}ms`, command);
81
83
  }
82
84
  });
@@ -0,0 +1,13 @@
1
+ import { Command } from '../command';
2
+ import { Logger } from '../logger';
3
+ import { FlowController } from './flow-controller';
4
+ export declare class LoggerPadding implements FlowController {
5
+ private readonly logger;
6
+ constructor({ logger }: {
7
+ logger: Logger;
8
+ });
9
+ handle(commands: Command[]): {
10
+ commands: Command[];
11
+ onFinish: () => void;
12
+ };
13
+ }
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LoggerPadding = void 0;
4
+ class LoggerPadding {
5
+ logger;
6
+ constructor({ logger }) {
7
+ this.logger = logger;
8
+ }
9
+ handle(commands) {
10
+ // Sometimes there's limited concurrency, so not all commands will spawn straight away.
11
+ // Compute the prefix length now, which works for all styles but those with a PID.
12
+ let length = commands.reduce((length, command) => {
13
+ const content = this.logger.getPrefixContent(command);
14
+ return Math.max(length, content?.value.length || 0);
15
+ }, 0);
16
+ this.logger.setPrefixLength(length);
17
+ // The length of prefixes is somewhat stable, except for PIDs, which might change when a
18
+ // process spawns (e.g. PIDs might look like 1, 10 or 100), therefore listen to command starts
19
+ // and update the prefix length when this happens.
20
+ const subs = commands.map((command) => command.timer.subscribe((event) => {
21
+ if (!event.endDate) {
22
+ const content = this.logger.getPrefixContent(command);
23
+ length = Math.max(length, content?.value.length || 0);
24
+ this.logger.setPrefixLength(length);
25
+ }
26
+ }));
27
+ return {
28
+ commands,
29
+ onFinish() {
30
+ subs.forEach((sub) => sub.unsubscribe());
31
+ },
32
+ };
33
+ }
34
+ }
35
+ exports.LoggerPadding = LoggerPadding;
@@ -2,16 +2,17 @@ import * as Rx from 'rxjs';
2
2
  import { Command } from '../command';
3
3
  import { Logger } from '../logger';
4
4
  import { FlowController } from './flow-controller';
5
+ export type RestartDelay = number | 'exponential';
5
6
  /**
6
7
  * Restarts commands that fail up to a defined number of times.
7
8
  */
8
9
  export declare class RestartProcess implements FlowController {
9
10
  private readonly logger;
10
11
  private readonly scheduler?;
11
- readonly delay: number;
12
+ private readonly delay;
12
13
  readonly tries: number;
13
14
  constructor({ delay, tries, logger, scheduler, }: {
14
- delay?: number;
15
+ delay?: RestartDelay;
15
16
  tries?: number;
16
17
  logger: Logger;
17
18
  scheduler?: Rx.SchedulerLike;
@@ -31,9 +31,13 @@ const defaults = __importStar(require("../defaults"));
31
31
  * Restarts commands that fail up to a defined number of times.
32
32
  */
33
33
  class RestartProcess {
34
+ logger;
35
+ scheduler;
36
+ delay;
37
+ tries;
34
38
  constructor({ delay, tries, logger, scheduler, }) {
35
39
  this.logger = logger;
36
- this.delay = delay != null ? +delay : defaults.restartDelay;
40
+ this.delay = delay ?? 0;
37
41
  this.tries = tries != null ? +tries : defaults.restartTries;
38
42
  this.tries = this.tries < 0 ? Infinity : this.tries;
39
43
  this.scheduler = scheduler;
@@ -42,12 +46,17 @@ class RestartProcess {
42
46
  if (this.tries === 0) {
43
47
  return { commands };
44
48
  }
49
+ const delayOperator = (0, operators_1.delayWhen)((_, index) => {
50
+ const { delay } = this;
51
+ const value = delay === 'exponential' ? Math.pow(2, index) * 1000 : delay;
52
+ return Rx.timer(value, this.scheduler);
53
+ });
45
54
  commands
46
55
  .map((command) => command.close.pipe((0, operators_1.take)(this.tries), (0, operators_1.takeWhile)(({ exitCode }) => exitCode !== 0)))
47
56
  .map((failure, index) => Rx.merge(
48
57
  // Delay the emission (so that the restarts happen on time),
49
58
  // explicitly telling the subscriber that a restart is needed
50
- failure.pipe((0, operators_1.delay)(this.delay, this.scheduler), (0, operators_1.map)(() => true)),
59
+ failure.pipe(delayOperator, (0, operators_1.map)(() => true)),
51
60
  // Skip the first N emissions (as these would be duplicates of the above),
52
61
  // meaning it will be empty because of success, or failed all N times,
53
62
  // and no more restarts should be attempted.
@@ -64,6 +73,9 @@ class RestartProcess {
64
73
  // We let all success codes pass, and failures only after restarting won't happen again
65
74
  return exitCode === 0 || emission >= this.tries;
66
75
  }));
76
+ // Return a proxy so that mutations happen on the original Command object.
77
+ // If either `Object.assign()` or `Object.create()` were used, it'd be hard to
78
+ // reflect the mutations on Command objects referenced by previous flow controllers.
67
79
  return new Proxy(command, {
68
80
  get(target, prop) {
69
81
  return prop === 'close' ? closeStream : target[prop];
@@ -0,0 +1,21 @@
1
+ import { Command, SpawnCommand } from '../command';
2
+ import { Logger } from '../logger';
3
+ import { FlowController } from './flow-controller';
4
+ export declare class Teardown implements FlowController {
5
+ private readonly logger;
6
+ private readonly spawn;
7
+ private readonly teardown;
8
+ constructor({ logger, spawn, commands, }: {
9
+ logger: Logger;
10
+ /**
11
+ * Which function to use to spawn commands.
12
+ * Defaults to the same used by the rest of concurrently.
13
+ */
14
+ spawn?: SpawnCommand;
15
+ commands: readonly string[];
16
+ });
17
+ handle(commands: Command[]): {
18
+ commands: Command[];
19
+ onFinish: () => Promise<void>;
20
+ };
21
+ }