concurrently 8.2.1 → 9.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 (63) hide show
  1. package/README.md +35 -240
  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 +27 -6
  15. package/dist/src/concurrently.d.ts +30 -5
  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.d.ts +1 -1
  44. package/dist/src/prefix-color-selector.js +3 -1
  45. package/dist/src/{get-spawn-opts.d.ts → spawn.d.ts} +20 -5
  46. package/dist/src/spawn.js +49 -0
  47. package/docs/README.md +13 -0
  48. package/docs/cli/configuration.md +11 -0
  49. package/docs/cli/input-handling.md +40 -0
  50. package/docs/cli/output-control.md +35 -0
  51. package/docs/cli/passthrough-arguments.md +80 -0
  52. package/docs/cli/prefixing.md +147 -0
  53. package/docs/cli/restarting.md +38 -0
  54. package/docs/cli/shortcuts.md +72 -0
  55. package/docs/demo.gif +0 -0
  56. package/index.d.mts +7 -0
  57. package/index.d.ts +11 -0
  58. package/index.js +6 -1
  59. package/index.mjs +2 -2
  60. package/package.json +41 -33
  61. package/dist/bin/epilogue.d.ts +0 -1
  62. package/dist/bin/epilogue.js +0 -90
  63. package/dist/src/get-spawn-opts.js +0 -18
@@ -0,0 +1,72 @@
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.Teardown = void 0;
27
+ const Rx = __importStar(require("rxjs"));
28
+ const spawn_1 = require("../spawn");
29
+ class Teardown {
30
+ logger;
31
+ spawn;
32
+ teardown;
33
+ constructor({ logger, spawn, commands, }) {
34
+ this.logger = logger;
35
+ this.spawn = spawn || spawn_1.spawn;
36
+ this.teardown = commands;
37
+ }
38
+ handle(commands) {
39
+ const { logger, teardown, spawn } = this;
40
+ const onFinish = async () => {
41
+ if (!teardown.length) {
42
+ return;
43
+ }
44
+ for (const command of teardown) {
45
+ logger.logGlobalEvent(`Running teardown command "${command}"`);
46
+ const child = spawn(command, (0, spawn_1.getSpawnOpts)({ stdio: 'raw' }));
47
+ const error = Rx.fromEvent(child, 'error');
48
+ const close = Rx.fromEvent(child, 'close');
49
+ try {
50
+ const [exitCode, signal] = await Promise.race([
51
+ Rx.firstValueFrom(error).then((event) => {
52
+ throw event;
53
+ }),
54
+ Rx.firstValueFrom(close).then((event) => event),
55
+ ]);
56
+ logger.logGlobalEvent(`Teardown command "${command}" exited with code ${exitCode ?? signal}`);
57
+ if (signal === 'SIGINT') {
58
+ break;
59
+ }
60
+ }
61
+ catch (error) {
62
+ const errorText = String(error instanceof Error ? error.stack || error : error);
63
+ logger.logGlobalEvent(`Teardown command "${command}" errored:`);
64
+ logger.logGlobalEvent(errorText);
65
+ return Promise.reject();
66
+ }
67
+ }
68
+ };
69
+ return { commands, onFinish };
70
+ }
71
+ }
72
+ exports.Teardown = Teardown;
@@ -1,7 +1,7 @@
1
1
  /// <reference types="node" />
2
2
  import { Readable } from 'stream';
3
3
  import { CloseEvent, Command, CommandIdentifier, TimerEvent } from './command';
4
- import { concurrently, ConcurrentlyCommandInput, ConcurrentlyOptions as BaseConcurrentlyOptions, ConcurrentlyResult } from './concurrently';
4
+ import { concurrently as createConcurrently, ConcurrentlyCommandInput, ConcurrentlyOptions as BaseConcurrentlyOptions, ConcurrentlyResult } from './concurrently';
5
5
  import { FlowController } from './flow-control/flow-controller';
6
6
  import { InputHandler } from './flow-control/input-handler';
7
7
  import { KillOnSignal } from './flow-control/kill-on-signal';
@@ -10,9 +10,9 @@ import { LogError } from './flow-control/log-error';
10
10
  import { LogExit } from './flow-control/log-exit';
11
11
  import { LogOutput } from './flow-control/log-output';
12
12
  import { LogTimings } from './flow-control/log-timings';
13
- import { RestartProcess } from './flow-control/restart-process';
13
+ import { RestartDelay, RestartProcess } from './flow-control/restart-process';
14
14
  import { Logger } from './logger';
15
- export type ConcurrentlyOptions = BaseConcurrentlyOptions & {
15
+ export type ConcurrentlyOptions = Omit<BaseConcurrentlyOptions, 'abortSignal' | 'hide'> & {
16
16
  /**
17
17
  * Which command(s) should have their output hidden.
18
18
  */
@@ -26,13 +26,17 @@ export type ConcurrentlyOptions = BaseConcurrentlyOptions & {
26
26
  * How many characters should a prefix have at most, used when the prefix format is `command`.
27
27
  */
28
28
  prefixLength?: number;
29
+ /**
30
+ * Pads short prefixes with spaces so that all prefixes have the same length.
31
+ */
32
+ padPrefix?: boolean;
29
33
  /**
30
34
  * Whether output should be formatted to include prefixes and whether "event" logs will be logged.
31
35
  */
32
36
  raw?: boolean;
33
37
  /**
34
38
  * Date format used when logging date/time.
35
- * @see https://date-fns.org/v2.0.1/docs/format
39
+ * @see https://www.unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
36
40
  */
37
41
  timestampFormat?: string;
38
42
  defaultInputTarget?: CommandIdentifier;
@@ -44,7 +48,7 @@ export type ConcurrentlyOptions = BaseConcurrentlyOptions & {
44
48
  *
45
49
  * @see RestartProcess
46
50
  */
47
- restartDelay?: number;
51
+ restartDelay?: RestartDelay;
48
52
  /**
49
53
  * How many times commands should be restarted when they exit with a failure.
50
54
  *
@@ -63,12 +67,18 @@ export type ConcurrentlyOptions = BaseConcurrentlyOptions & {
63
67
  * @see LogTimings
64
68
  */
65
69
  timings?: boolean;
70
+ /**
71
+ * Clean up command(s) to execute before exiting concurrently.
72
+ * These won't be prefixed and don't affect concurrently's exit code.
73
+ */
74
+ teardown?: readonly string[];
66
75
  /**
67
76
  * List of additional arguments passed that will get replaced in each command.
68
77
  * If not defined, no argument replacing will happen.
69
78
  */
70
79
  additionalArguments?: string[];
71
80
  };
72
- declare const _default: (commands: ConcurrentlyCommandInput[], options?: Partial<ConcurrentlyOptions>) => ConcurrentlyResult;
73
- export default _default;
74
- export { CloseEvent, Command, CommandIdentifier, concurrently, ConcurrentlyCommandInput, ConcurrentlyResult, FlowController, InputHandler, KillOnSignal, KillOthers, LogError, LogExit, Logger, LogOutput, LogTimings, RestartProcess, TimerEvent, };
81
+ export declare function concurrently(commands: ConcurrentlyCommandInput[], options?: Partial<ConcurrentlyOptions>): ConcurrentlyResult;
82
+ export { ConcurrentlyCommandInput, ConcurrentlyResult, createConcurrently, Logger };
83
+ export { CloseEvent, Command, CommandIdentifier, TimerEvent };
84
+ export { FlowController, InputHandler, KillOnSignal, KillOthers, LogError, LogExit, LogOutput, LogTimings, RestartProcess, };
package/dist/src/index.js CHANGED
@@ -1,10 +1,14 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RestartProcess = exports.LogTimings = exports.LogOutput = exports.Logger = exports.LogExit = exports.LogError = exports.KillOthers = exports.KillOnSignal = exports.InputHandler = exports.concurrently = exports.Command = void 0;
6
+ exports.RestartProcess = exports.LogTimings = exports.LogOutput = exports.LogExit = exports.LogError = exports.KillOthers = exports.KillOnSignal = exports.InputHandler = exports.Command = exports.Logger = exports.createConcurrently = exports.concurrently = void 0;
7
+ const lodash_1 = __importDefault(require("lodash"));
4
8
  const command_1 = require("./command");
5
9
  Object.defineProperty(exports, "Command", { enumerable: true, get: function () { return command_1.Command; } });
6
10
  const concurrently_1 = require("./concurrently");
7
- Object.defineProperty(exports, "concurrently", { enumerable: true, get: function () { return concurrently_1.concurrently; } });
11
+ Object.defineProperty(exports, "createConcurrently", { enumerable: true, get: function () { return concurrently_1.concurrently; } });
8
12
  const input_handler_1 = require("./flow-control/input-handler");
9
13
  Object.defineProperty(exports, "InputHandler", { enumerable: true, get: function () { return input_handler_1.InputHandler; } });
10
14
  const kill_on_signal_1 = require("./flow-control/kill-on-signal");
@@ -19,27 +23,41 @@ const log_output_1 = require("./flow-control/log-output");
19
23
  Object.defineProperty(exports, "LogOutput", { enumerable: true, get: function () { return log_output_1.LogOutput; } });
20
24
  const log_timings_1 = require("./flow-control/log-timings");
21
25
  Object.defineProperty(exports, "LogTimings", { enumerable: true, get: function () { return log_timings_1.LogTimings; } });
26
+ const logger_padding_1 = require("./flow-control/logger-padding");
22
27
  const restart_process_1 = require("./flow-control/restart-process");
23
28
  Object.defineProperty(exports, "RestartProcess", { enumerable: true, get: function () { return restart_process_1.RestartProcess; } });
29
+ const teardown_1 = require("./flow-control/teardown");
24
30
  const logger_1 = require("./logger");
25
31
  Object.defineProperty(exports, "Logger", { enumerable: true, get: function () { return logger_1.Logger; } });
26
- exports.default = (commands, options = {}) => {
32
+ function concurrently(commands, options = {}) {
33
+ // To avoid empty strings from hiding the output of commands that don't have a name,
34
+ // keep in the list of commands to hide only strings with some length.
35
+ // This might happen through the CLI when no `--hide` argument is specified, for example.
36
+ const hide = lodash_1.default.castArray(options.hide).filter((id) => id || id === 0);
27
37
  const logger = new logger_1.Logger({
28
- hide: options.hide,
38
+ hide,
29
39
  prefixFormat: options.prefix,
30
- prefixLength: options.prefixLength,
40
+ commandLength: options.prefixLength,
31
41
  raw: options.raw,
32
42
  timestampFormat: options.timestampFormat,
33
43
  });
44
+ if (options.prefixColors === false) {
45
+ logger.toggleColors(false);
46
+ }
47
+ const abortController = new AbortController();
34
48
  return (0, concurrently_1.concurrently)(commands, {
35
49
  maxProcesses: options.maxProcesses,
36
50
  raw: options.raw,
37
51
  successCondition: options.successCondition,
38
52
  cwd: options.cwd,
53
+ hide,
39
54
  logger,
40
55
  outputStream: options.outputStream || process.stdout,
41
56
  group: options.group,
57
+ abortSignal: abortController.signal,
42
58
  controllers: [
59
+ // LoggerPadding needs to run before any other controllers that might output something
60
+ ...(options.padPrefix ? [new logger_padding_1.LoggerPadding({ logger })] : []),
43
61
  new log_error_1.LogError({ logger }),
44
62
  new log_output_1.LogOutput({ logger }),
45
63
  new log_exit_1.LogExit({ logger }),
@@ -49,7 +67,7 @@ exports.default = (commands, options = {}) => {
49
67
  inputStream: options.inputStream || (options.handleInput ? process.stdin : undefined),
50
68
  pauseInputStreamOnFinish: options.pauseInputStreamOnFinish,
51
69
  }),
52
- new kill_on_signal_1.KillOnSignal({ process }),
70
+ new kill_on_signal_1.KillOnSignal({ process, abortController }),
53
71
  new restart_process_1.RestartProcess({
54
72
  logger,
55
73
  delay: options.restartDelay,
@@ -59,13 +77,16 @@ exports.default = (commands, options = {}) => {
59
77
  logger,
60
78
  conditions: options.killOthers || [],
61
79
  killSignal: options.killSignal,
80
+ abortController,
62
81
  }),
63
82
  new log_timings_1.LogTimings({
64
83
  logger: options.timings ? logger : undefined,
65
84
  timestampFormat: options.timestampFormat,
66
85
  }),
86
+ new teardown_1.Teardown({ logger, spawn: options.spawn, commands: options.teardown || [] }),
67
87
  ],
68
88
  prefixColors: options.prefixColors || [],
69
89
  additionalArguments: options.additionalArguments,
70
90
  });
71
- };
91
+ }
92
+ exports.concurrently = concurrently;
@@ -4,13 +4,19 @@ export declare class Logger {
4
4
  private readonly hide;
5
5
  private readonly raw;
6
6
  private readonly prefixFormat?;
7
- private readonly prefixLength;
8
- private readonly timestampFormat;
7
+ private readonly commandLength;
8
+ private readonly dateFormatter;
9
+ private chalk;
9
10
  /**
10
- * Last character emitted.
11
+ * How many characters should a prefix have.
12
+ * Prefixes shorter than this will be padded with spaces to the right.
13
+ */
14
+ private prefixLength;
15
+ /**
16
+ * Last character emitted, and from which command.
11
17
  * If `undefined`, then nothing has been logged yet.
12
18
  */
13
- private lastChar?;
19
+ private lastWrite?;
14
20
  /**
15
21
  * Observable that emits when there's been output logged.
16
22
  * If `command` is is `undefined`, then the log is for a global event.
@@ -19,11 +25,11 @@ export declare class Logger {
19
25
  command: Command | undefined;
20
26
  text: string;
21
27
  }>;
22
- constructor({ hide, prefixFormat, prefixLength, raw, timestampFormat, }: {
28
+ constructor({ hide, prefixFormat, commandLength, raw, timestampFormat, }: {
23
29
  /**
24
- * Which command(s) should have their output hidden.
30
+ * Which commands should have their output hidden.
25
31
  */
26
- hide?: CommandIdentifier | CommandIdentifier[];
32
+ hide?: CommandIdentifier[];
27
33
  /**
28
34
  * Whether output should be formatted to include prefixes and whether "event" logs will be
29
35
  * logged.
@@ -35,18 +41,27 @@ export declare class Logger {
35
41
  */
36
42
  prefixFormat?: string;
37
43
  /**
38
- * How many characters should a prefix have at most, used when the prefix format is `command`.
44
+ * How many characters should a prefix have at most when the format is `command`.
39
45
  */
40
- prefixLength?: number;
46
+ commandLength?: number;
41
47
  /**
42
48
  * Date format used when logging date/time.
43
- * @see https://date-fns.org/v2.0.1/docs/format
49
+ * @see https://www.unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
44
50
  */
45
51
  timestampFormat?: string;
46
52
  });
53
+ /**
54
+ * Toggles colors on/off globally.
55
+ */
56
+ toggleColors(on: boolean): void;
47
57
  private shortenText;
48
58
  private getPrefixesFor;
59
+ getPrefixContent(command: Command): {
60
+ type: 'default' | 'template';
61
+ value: string;
62
+ } | undefined;
49
63
  getPrefix(command: Command): string;
64
+ setPrefixLength(length: number): void;
50
65
  colorText(command: Command, text: string): string;
51
66
  /**
52
67
  * Logs an event for a command (e.g. start, stop).
@@ -28,34 +28,53 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
29
  exports.Logger = void 0;
30
30
  const chalk_1 = __importDefault(require("chalk"));
31
- const format_1 = __importDefault(require("date-fns/format"));
32
31
  const lodash_1 = __importDefault(require("lodash"));
33
32
  const Rx = __importStar(require("rxjs"));
33
+ const date_format_1 = require("./date-format");
34
34
  const defaults = __importStar(require("./defaults"));
35
+ const defaultChalk = chalk_1.default;
36
+ const noColorChalk = new chalk_1.default.Instance({ level: 0 });
35
37
  class Logger {
36
- constructor({ hide, prefixFormat, prefixLength, raw = false, timestampFormat, }) {
37
- /**
38
- * Observable that emits when there's been output logged.
39
- * If `command` is is `undefined`, then the log is for a global event.
40
- */
41
- this.output = new Rx.Subject();
42
- // To avoid empty strings from hiding the output of commands that don't have a name,
43
- // keep in the list of commands to hide only strings with some length.
44
- // This might happen through the CLI when no `--hide` argument is specified, for example.
45
- this.hide = lodash_1.default.castArray(hide)
46
- .filter((name) => name || name === 0)
47
- .map(String);
38
+ hide;
39
+ raw;
40
+ prefixFormat;
41
+ commandLength;
42
+ dateFormatter;
43
+ chalk = defaultChalk;
44
+ /**
45
+ * How many characters should a prefix have.
46
+ * Prefixes shorter than this will be padded with spaces to the right.
47
+ */
48
+ prefixLength = 0;
49
+ /**
50
+ * Last character emitted, and from which command.
51
+ * If `undefined`, then nothing has been logged yet.
52
+ */
53
+ lastWrite;
54
+ /**
55
+ * Observable that emits when there's been output logged.
56
+ * If `command` is is `undefined`, then the log is for a global event.
57
+ */
58
+ output = new Rx.Subject();
59
+ constructor({ hide, prefixFormat, commandLength, raw = false, timestampFormat, }) {
60
+ this.hide = (hide || []).map(String);
48
61
  this.raw = raw;
49
62
  this.prefixFormat = prefixFormat;
50
- this.prefixLength = prefixLength || defaults.prefixLength;
51
- this.timestampFormat = timestampFormat || defaults.timestampFormat;
63
+ this.commandLength = commandLength || defaults.prefixLength;
64
+ this.dateFormatter = new date_format_1.DateFormatter(timestampFormat || defaults.timestampFormat);
65
+ }
66
+ /**
67
+ * Toggles colors on/off globally.
68
+ */
69
+ toggleColors(on) {
70
+ this.chalk = on ? defaultChalk : noColorChalk;
52
71
  }
53
72
  shortenText(text) {
54
- if (!text || text.length <= this.prefixLength) {
73
+ if (!text || text.length <= this.commandLength) {
55
74
  return text;
56
75
  }
57
76
  const ellipsis = '..';
58
- const prefixLength = this.prefixLength - ellipsis.length;
77
+ const prefixLength = this.commandLength - ellipsis.length;
59
78
  const endLength = Math.floor(prefixLength / 2);
60
79
  const beginningLength = prefixLength - endLength;
61
80
  const beginnning = text.slice(0, beginningLength);
@@ -64,35 +83,50 @@ class Logger {
64
83
  }
65
84
  getPrefixesFor(command) {
66
85
  return {
67
- pid: String(command.pid),
86
+ // When there's limited concurrency, the PID might not be immediately available,
87
+ // so avoid the string 'undefined' from becoming a prefix
88
+ pid: command.pid != null ? String(command.pid) : '',
68
89
  index: String(command.index),
69
90
  name: command.name,
70
91
  command: this.shortenText(command.command),
71
- time: (0, format_1.default)(Date.now(), this.timestampFormat),
92
+ time: this.dateFormatter.format(new Date()),
72
93
  };
73
94
  }
74
- getPrefix(command) {
95
+ getPrefixContent(command) {
75
96
  const prefix = this.prefixFormat || (command.name ? 'name' : 'index');
76
97
  if (prefix === 'none') {
77
- return '';
98
+ return;
78
99
  }
79
100
  const prefixes = this.getPrefixesFor(command);
80
101
  if (Object.keys(prefixes).includes(prefix)) {
81
- return `[${prefixes[prefix]}]`;
102
+ return { type: 'default', value: prefixes[prefix] };
82
103
  }
83
- return lodash_1.default.reduce(prefixes, (prev, val, key) => {
104
+ const value = lodash_1.default.reduce(prefixes, (prev, val, key) => {
84
105
  const keyRegex = new RegExp(lodash_1.default.escapeRegExp(`{${key}}`), 'g');
85
106
  return prev.replace(keyRegex, String(val));
86
107
  }, prefix);
108
+ return { type: 'template', value };
109
+ }
110
+ getPrefix(command) {
111
+ const content = this.getPrefixContent(command);
112
+ if (!content) {
113
+ return '';
114
+ }
115
+ return content.type === 'template'
116
+ ? content.value.padEnd(this.prefixLength, ' ')
117
+ : `[${content.value.padEnd(this.prefixLength, ' ')}]`;
118
+ }
119
+ setPrefixLength(length) {
120
+ this.prefixLength = length;
87
121
  }
88
122
  colorText(command, text) {
89
123
  let color;
90
124
  if (command.prefixColor && command.prefixColor.startsWith('#')) {
91
- color = chalk_1.default.hex(command.prefixColor);
125
+ color = this.chalk.hex(command.prefixColor);
92
126
  }
93
127
  else {
94
- const defaultColor = lodash_1.default.get(chalk_1.default, defaults.prefixColors, chalk_1.default.reset);
95
- color = lodash_1.default.get(chalk_1.default, command.prefixColor ?? '', defaultColor);
128
+ const defaultColor = lodash_1.default.get(this.chalk, defaults.prefixColors, this.chalk.reset);
129
+ color = lodash_1.default.get(this.chalk, command.prefixColor ?? '', defaultColor);
96
130
  }
97
131
  return color(text);
98
132
  }
@@ -105,7 +139,14 @@ class Logger {
105
139
  if (this.raw) {
106
140
  return;
107
141
  }
108
- this.logCommandText(chalk_1.default.reset(text) + '\n', command);
142
+ // Last write was from this command, but it didn't end with a line feed.
143
+ // Prepend one, otherwise the event's text will be concatenated to that write.
144
+ // A line feed is otherwise inserted anyway.
145
+ let prefix = '';
146
+ if (this.lastWrite?.command === command && this.lastWrite.char !== '\n') {
147
+ prefix = '\n';
148
+ }
149
+ this.logCommandText(prefix + this.chalk.reset(text) + '\n', command);
109
150
  }
110
151
  logCommandText(text, command) {
111
152
  if (this.hide.includes(String(command.index)) || this.hide.includes(command.name)) {
@@ -123,7 +164,7 @@ class Logger {
123
164
  if (this.raw) {
124
165
  return;
125
166
  }
126
- this.log(chalk_1.default.reset('-->') + ' ', chalk_1.default.reset(text) + '\n');
167
+ this.log(this.chalk.reset('-->') + ' ', this.chalk.reset(text) + '\n');
127
168
  }
128
169
  /**
129
170
  * Logs a table from an input object array, like `console.table`.
@@ -181,21 +222,19 @@ class Logger {
181
222
  }
182
223
  // #70 - replace some ANSI code that would impact clearing lines
183
224
  text = text.replace(/\u2026/g, '...');
184
- const lines = text.split('\n').map((line, index, lines) => {
185
- // First line will write prefix only if we finished the last write with a LF.
186
- // Last line won't write prefix because it should be empty.
187
- if (index === 0 || index === lines.length - 1) {
188
- return line;
189
- }
190
- return prefix + line;
191
- });
192
- if (!this.lastChar || this.lastChar === '\n') {
225
+ // This write's interrupting another command, emit a line feed to start clean.
226
+ if (this.lastWrite && this.lastWrite.command !== command && this.lastWrite.char !== '\n') {
227
+ this.emit(this.lastWrite.command, '\n');
228
+ }
229
+ // Clean lines should emit a prefix
230
+ if (!this.lastWrite || this.lastWrite.char === '\n') {
193
231
  this.emit(command, prefix);
194
232
  }
195
- this.lastChar = text[text.length - 1];
196
- this.emit(command, lines.join('\n'));
233
+ const textToWrite = text.replaceAll('\n', (lf, i) => lf + (text[i + 1] ? prefix : ''));
234
+ this.emit(command, textToWrite);
197
235
  }
198
236
  emit(command, text) {
237
+ this.lastWrite = { command, char: text[text.length - 1] };
199
238
  this.output.next({ command, text });
200
239
  }
201
240
  }
@@ -29,8 +29,11 @@ const Rx = __importStar(require("rxjs"));
29
29
  * Class responsible for actually writing output onto a writable stream.
30
30
  */
31
31
  class OutputWriter {
32
+ outputStream;
33
+ group;
34
+ buffers;
35
+ activeCommandIndex = 0;
32
36
  constructor({ outputStream, group, commands, }) {
33
- this.activeCommandIndex = 0;
34
37
  this.outputStream = outputStream;
35
38
  this.group = group;
36
39
  this.buffers = commands.map(() => []);
@@ -42,7 +45,8 @@ class OutputWriter {
42
45
  for (let i = command.index + 1; i < commands.length; i++) {
43
46
  this.activeCommandIndex = i;
44
47
  this.flushBuffer(i);
45
- if (!commands[i].exited) {
48
+ // TODO: Should errored commands also flush buffer?
49
+ if (commands[i].state !== 'exited') {
46
50
  break;
47
51
  }
48
52
  }
@@ -1,7 +1,7 @@
1
1
  import chalk from 'chalk';
2
2
  export declare class PrefixColorSelector {
3
3
  private colorGenerator;
4
- constructor(customColors?: string[]);
4
+ constructor(customColors?: string | string[]);
5
5
  /** A list of colors that are readable in a terminal. */
6
6
  static get ACCEPTABLE_CONSOLE_COLORS(): ("stderr" | keyof chalk.Chalk | "supportsColor" | "Level" | "Color" | "ForegroundColor" | "BackgroundColor" | "Modifiers")[];
7
7
  /**
@@ -52,8 +52,10 @@ function* createColorGenerator(customColors) {
52
52
  }
53
53
  }
54
54
  class PrefixColorSelector {
55
+ colorGenerator;
55
56
  constructor(customColors = []) {
56
- this.colorGenerator = createColorGenerator(customColors);
57
+ const normalizedColors = typeof customColors === 'string' ? [customColors] : customColors;
58
+ this.colorGenerator = createColorGenerator(normalizedColors);
57
59
  }
58
60
  /** A list of colors that are readable in a terminal. */
59
61
  static get ACCEPTABLE_CONSOLE_COLORS() {
@@ -3,9 +3,14 @@
3
3
  /// <reference types="node" />
4
4
  /// <reference types="node" />
5
5
  /// <reference types="node" />
6
- import { SpawnOptions } from 'child_process';
6
+ /// <reference types="node" />
7
+ import { ChildProcess, SpawnOptions } from 'child_process';
7
8
  import supportsColor from 'supports-color';
8
- export declare const getSpawnOpts: ({ colorSupport, cwd, process, raw, env, }: {
9
+ /**
10
+ * Spawns a command using `cmd.exe` on Windows, or `/bin/sh` elsewhere.
11
+ */
12
+ export declare function spawn(command: string, options: SpawnOptions, spawn?: (command: string, args: string[], options: SpawnOptions) => ChildProcess, process?: Pick<NodeJS.Process, 'platform'>): ChildProcess;
13
+ export declare const getSpawnOpts: ({ colorSupport, cwd, process, ipc, stdio, env, }: {
9
14
  /**
10
15
  * What the color support of the spawned processes should be.
11
16
  * If set to `false`, then no colors should be output.
@@ -23,10 +28,20 @@ export declare const getSpawnOpts: ({ colorSupport, cwd, process, raw, env, }: {
23
28
  */
24
29
  cwd?: string | undefined;
25
30
  /**
26
- * Whether to customize the options for spawning processes in raw mode.
27
- * Defaults to false.
31
+ * The file descriptor number at which the channel for inter-process communication
32
+ * should be set up.
33
+ */
34
+ ipc?: number | undefined;
35
+ /**
36
+ * Which stdio mode to use. Raw implies inheriting the parent process' stdio.
37
+ *
38
+ * - `normal`: all of stdout, stderr and stdin are piped
39
+ * - `hidden`: stdin is piped, stdout/stderr outputs are ignored
40
+ * - `raw`: all of stdout, stderr and stdin are inherited from the main process
41
+ *
42
+ * Defaults to `normal`.
28
43
  */
29
- raw?: boolean | undefined;
44
+ stdio?: "raw" | "hidden" | "normal" | undefined;
30
45
  /**
31
46
  * Map of custom environment variables to include in the spawn options.
32
47
  */
@@ -0,0 +1,49 @@
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.getSpawnOpts = exports.spawn = void 0;
7
+ const assert_1 = __importDefault(require("assert"));
8
+ const child_process_1 = require("child_process");
9
+ const supports_color_1 = __importDefault(require("supports-color"));
10
+ /**
11
+ * Spawns a command using `cmd.exe` on Windows, or `/bin/sh` elsewhere.
12
+ */
13
+ // Implementation based off of https://github.com/mmalecki/spawn-command/blob/v0.0.2-1/lib/spawn-command.js
14
+ function spawn(command, options,
15
+ // For testing
16
+ spawn = child_process_1.spawn, process = global.process) {
17
+ let file = '/bin/sh';
18
+ let args = ['-c', command];
19
+ if (process.platform === 'win32') {
20
+ file = 'cmd.exe';
21
+ args = ['/s', '/c', `"${command}"`];
22
+ options.windowsVerbatimArguments = true;
23
+ }
24
+ return spawn(file, args, options);
25
+ }
26
+ exports.spawn = spawn;
27
+ const getSpawnOpts = ({ colorSupport = supports_color_1.default.stdout, cwd, process = global.process, ipc, stdio = 'normal', env = {}, }) => {
28
+ const stdioValues = stdio === 'normal'
29
+ ? ['pipe', 'pipe', 'pipe']
30
+ : stdio === 'raw'
31
+ ? ['inherit', 'inherit', 'inherit']
32
+ : ['pipe', 'ignore', 'ignore'];
33
+ if (ipc != null) {
34
+ // Avoid overriding the stdout/stderr/stdin
35
+ assert_1.default.ok(ipc > 2, '[concurrently] the IPC channel number should be > 2');
36
+ stdioValues[ipc] = 'ipc';
37
+ }
38
+ return {
39
+ cwd: cwd || process.cwd(),
40
+ stdio: stdioValues,
41
+ ...(/^win/.test(process.platform) && { detached: false }),
42
+ env: {
43
+ ...(colorSupport ? { FORCE_COLOR: colorSupport.level.toString() } : {}),
44
+ ...process.env,
45
+ ...env,
46
+ },
47
+ };
48
+ };
49
+ exports.getSpawnOpts = getSpawnOpts;
package/docs/README.md ADDED
@@ -0,0 +1,13 @@
1
+ # Concurrently Documentation
2
+
3
+ ## CLI
4
+
5
+ These articles cover using concurrently through CLI:
6
+
7
+ - [Prefixing](./cli/prefixing.md)
8
+ - [Output Control](./cli/output-control.md)
9
+ - [Shortcuts](./cli/shortcuts.md)
10
+ - [Restarting Commands](./cli/restarting.md)
11
+ - [Input Handling](./cli/input-handling.md)
12
+ - [Passthrough Arguments](./cli/passthrough-arguments.md)
13
+ - [Configuration](./cli/configuration.md)
@@ -0,0 +1,11 @@
1
+ # Configuration
2
+
3
+ You might want to configure concurrently to always have certain flags on.
4
+ Any of concurrently's flags can be set via environment variables that are prefixed with `CONCURRENTLY_`.
5
+
6
+ ```bash
7
+ $ export CONCURRENTLY_KILL_OTHERS=true
8
+ $ export CONCURRENTLY_HANDLE_INPUT=true
9
+ # Equivalent to passing --kill-others and --handle-input
10
+ $ concurrently nodemon "echo 'hey nodemon, you won't last long'"
11
+ ```