concurrently 4.0.1 → 5.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.
package/.travis.yml CHANGED
@@ -1,8 +1,11 @@
1
1
  language: node_js
2
2
  node_js:
3
+ - 12
3
4
  - 10
4
5
  - 8
5
6
  - 6
6
7
  script:
7
8
  - npm test
8
9
  - npm run lint
10
+ after_success:
11
+ - npm run report-coverage
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "eslint.enable": true,
3
3
  "jest.autoEnable": true,
4
- "jest.showCoverageOnLoad": true
4
+ "jest.showCoverageOnLoad": true,
5
+ "editor.rulers": [100]
5
6
  }
package/CONTRIBUTING.md CHANGED
@@ -6,7 +6,6 @@ updated.
6
6
 
7
7
  **Pull requests:** You don't need to bump version numbers or modify anything related to releasing. That stuff is fully automated, just write the functionality.
8
8
 
9
-
10
9
  # Maintaining
11
10
 
12
11
  ## Test
@@ -14,11 +13,9 @@ updated.
14
13
  Tests can be run with command:
15
14
 
16
15
  ```bash
17
- mocha
16
+ npm run test
18
17
  ```
19
18
 
20
- You need to have *mocha* installed globally with `npm install -g mocha`.
21
-
22
19
  ## Release
23
20
 
24
21
  * Commit all changes
package/README.md CHANGED
@@ -147,7 +147,7 @@ Prefix styling
147
147
  prefix when it is set to "command"
148
148
  [number] [default: 10]
149
149
  -t, --timestamp-format Specify the timestamp in moment/date-fns format.
150
- [string] [default: "YYYY-MM-DD HH:mm:ss.SSS"]
150
+ [string] [default: "yyyy-MM-dd HH:mm:ss.SSS"]
151
151
 
152
152
  Input handling
153
153
  -i, --handle-input Whether input should be forwarded to the child
@@ -225,30 +225,30 @@ For more details, visit https://github.com/kimmobrunfeldt/concurrently
225
225
  concurrently can be used programmatically by using the API documented below:
226
226
 
227
227
  ### `concurrently(commands[, options])`
228
- - `commands`: an array of either strings (containing the commands to run) or objects
228
+ - `commands`: an array of either strings (containing the commands to run) or objects
229
229
  with the shape `{ command, name, prefixColor }`.
230
230
  - `options` (optional): an object containing any of the below:
231
- - `defaultInputTarget`: the default input target when reading from `inputStream`.
231
+ - `defaultInputTarget`: the default input target when reading from `inputStream`.
232
232
  Default: `0`.
233
- - `inputStream`: a [`Readable` stream](https://nodejs.org/dist/latest-v10.x/docs/api/stream.html#stream_readable_streams)
233
+ - `inputStream`: a [`Readable` stream](https://nodejs.org/dist/latest-v10.x/docs/api/stream.html#stream_readable_streams)
234
234
  to read the input from, eg `process.stdin`.
235
- - `killOthers`: an array of exitting conditions that will cause a process to kill others.
235
+ - `killOthers`: an array of exitting conditions that will cause a process to kill others.
236
236
  Can contain any of `success` or `failure`.
237
237
  - `outputStream`: a [`Writable` stream](https://nodejs.org/dist/latest-v10.x/docs/api/stream.html#stream_writable_streams)
238
238
  to write logs to. Default: `process.stdout`.
239
- - `prefix`: the prefix type to use when logging processes output.
240
- Possible values: `index`, `pid`, `time`, `command`, `name`, `none`, or a template (eg `[{time} process: {pid}]`).
239
+ - `prefix`: the prefix type to use when logging processes output.
240
+ Possible values: `index`, `pid`, `time`, `command`, `name`, `none`, or a template (eg `[{time} process: {pid}]`).
241
241
  Default: the name of the process, or its index if no name is set.
242
242
  - `prefixLength`: how many characters to show when prefixing with `command`. Default: `10`
243
243
  - `raw`: whether raw mode should be used, meaning strictly process output will
244
244
  be logged, without any prefixes, colouring or extra stuff.
245
245
  - `successCondition`: the condition to consider the run was successful.
246
- If `first`, only the first process will make up the success of the run; if `last`, the last.
246
+ If `first`, only the first process to exit will make up the success of the run; if `last`, the last process that exits will determine whether the run succeeds.
247
247
  Anything else means all processes should exit successfully.
248
248
  - `restartTries`: how many attempts to restart a process that dies will be made. Default: `0`.
249
249
  - `restartDelay`: how many milliseconds to wait between process restarts. Default: `0`.
250
- - `timestampFormat`: a [date-fns/moment format](https://date-fns.org/v1.29.0/docs/format)
251
- to use when prefixing with `time`. Default: `YYYY-MM-DD HH:mm:ss.ZZZ`
250
+ - `timestampFormat`: a [date-fns format](https://date-fns.org/v2.0.1/docs/format)
251
+ to use when prefixing with `time`. Default: `yyyy-MM-dd HH:mm:ss.ZZZ`
252
252
 
253
253
  > Returns: a `Promise` that resolves if the run was successful (according to `successCondition` option),
254
254
  > or rejects otherwise.
@@ -258,7 +258,7 @@ Example:
258
258
  ```js
259
259
  const concurrently = require('concurrently');
260
260
  concurrently([
261
- 'npm:watch-*',
261
+ 'npm:watch-*',
262
262
  { command: 'nodemon', name: 'server' }
263
263
  ], {
264
264
  prefix: 'name',
@@ -282,3 +282,6 @@ concurrently([
282
282
  So *null* means the process didn't terminate normally. This will make **concurrent**
283
283
  to return non-zero exit code too.
284
284
 
285
+ * Does this work with the npm-replacement [yarn](https://github.com/yarnpkg/yarn)?
286
+
287
+ Yes! In all examples above, you may replace "`npm`" with "`yarn`".
package/appveyor.yml CHANGED
@@ -1,5 +1,6 @@
1
1
  environment:
2
2
  matrix:
3
+ - nodejs_version: '12'
3
4
  - nodejs_version: '10'
4
5
  - nodejs_version: '8'
5
6
  - nodejs_version: '6'
@@ -8,7 +8,7 @@ const args = yargs
8
8
  .usage('$0 [options] <command ...>')
9
9
  .help('h')
10
10
  .alias('h', 'help')
11
- .version('v')
11
+ .version('v', require('../package.json').version)
12
12
  .alias('v', 'V')
13
13
  .alias('v', 'version')
14
14
  .options({
@@ -154,6 +154,7 @@ concurrently(args._.map((command, index) => {
154
154
  : (args.killOthersOnFail ? ['failure'] : []),
155
155
  raw: args.raw,
156
156
  prefix: args.prefix,
157
+ prefixLength: args.prefixLength,
157
158
  restartDelay: args.restartAfter,
158
159
  restartTries: args.restartTries,
159
160
  successCondition: args.success,
@@ -13,7 +13,12 @@ const createKillMessage = prefix => new RegExp(
13
13
 
14
14
  const run = args => {
15
15
  const child = spawn('node ./concurrently.js ' + args, {
16
- cwd: __dirname
16
+ cwd: __dirname,
17
+ env: Object.assign({}, process.env, {
18
+ // When upgrading from jest 23 -> 24, colors started printing in the test output.
19
+ // They are forcibly disabled here
20
+ FORCE_COLOR: 0
21
+ }),
17
22
  });
18
23
 
19
24
  const stdout = readline.createInterface({
@@ -207,6 +212,26 @@ describe('--prefix', () => {
207
212
  });
208
213
  });
209
214
 
215
+ describe('--prefix-length', () => {
216
+ it('is alised to -l', done => {
217
+ const child = run('-p command -l 5 "echo foo" "echo bar"');
218
+ child.log.pipe(buffer(child.close)).subscribe(lines => {
219
+ expect(lines).toContainEqual(expect.stringContaining('[ec..o] foo'));
220
+ expect(lines).toContainEqual(expect.stringContaining('[ec..r] bar'));
221
+ done();
222
+ }, done);
223
+ });
224
+
225
+ it('specifies custom prefix length', done => {
226
+ const child = run('--prefix command --prefix-length 5 "echo foo" "echo bar"');
227
+ child.log.pipe(buffer(child.close)).subscribe(lines => {
228
+ expect(lines).toContainEqual(expect.stringContaining('[ec..o] foo'));
229
+ expect(lines).toContainEqual(expect.stringContaining('[ec..r] bar'));
230
+ done();
231
+ }, done);
232
+ });
233
+ });
234
+
210
235
  describe('--restart-tries', () => {
211
236
  it('changes how many times a command will restart', done => {
212
237
  const child = run('--restart-tries 1 "exit 1"');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "concurrently",
3
- "version": "4.0.1",
3
+ "version": "5.0.0",
4
4
  "description": "Run commands concurrently",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -11,6 +11,7 @@
11
11
  },
12
12
  "scripts": {
13
13
  "lint": "eslint . --ignore-path .gitignore",
14
+ "report-coverage": "cat coverage/lcov.info | coveralls",
14
15
  "test": "jest"
15
16
  },
16
17
  "repository": {
@@ -28,19 +29,20 @@
28
29
  "author": "Kimmo Brunfeldt",
29
30
  "license": "MIT",
30
31
  "dependencies": {
31
- "chalk": "^2.4.1",
32
- "date-fns": "^1.23.0",
33
- "lodash": "^4.17.10",
32
+ "chalk": "^2.4.2",
33
+ "date-fns": "^2.0.1",
34
+ "lodash": "^4.17.15",
34
35
  "read-pkg": "^4.0.1",
35
- "rxjs": "6.2.2",
36
+ "rxjs": "^6.5.2",
36
37
  "spawn-command": "^0.0.2-1",
37
38
  "supports-color": "^4.5.0",
38
- "tree-kill": "^1.1.0",
39
- "yargs": "^12.0.1"
39
+ "tree-kill": "^1.2.1",
40
+ "yargs": "^12.0.5"
40
41
  },
41
42
  "devDependencies": {
42
- "eslint": "^5.4.0",
43
- "jest": "^23.5.0",
43
+ "coveralls": "^3.0.4",
44
+ "eslint": "^5.16.0",
45
+ "jest": "^24.8.0",
44
46
  "jest-create-mock-instance": "^1.1.0"
45
47
  },
46
48
  "jest": {
@@ -1,13 +1,13 @@
1
1
  module.exports = class ExpandNpmShortcut {
2
2
  parse(commandInfo) {
3
- const [, cmdName, args] = commandInfo.command.match(/^npm:(\S+)(.*)/) || [];
3
+ const [, npmCmd, cmdName, args] = commandInfo.command.match(/^(npm|yarn):(\S+)(.*)/) || [];
4
4
  if (!cmdName) {
5
5
  return commandInfo;
6
6
  }
7
7
 
8
8
  return Object.assign({}, commandInfo, {
9
9
  name: commandInfo.name || cmdName,
10
- command: `npm run ${cmdName}${args}`
10
+ command: `${npmCmd} run ${cmdName}${args}`
11
11
  });
12
12
  }
13
13
  };
@@ -9,25 +9,28 @@ it('returns same command if no npm: prefix is present', () => {
9
9
  expect(parser.parse(commandInfo)).toBe(commandInfo);
10
10
  });
11
11
 
12
- describe('with npm: prefix', () => {
13
- it('expands to "npm run <script> <args>"', () => {
14
- const commandInfo = {
15
- name: 'echo',
16
- command: 'npm:foo -- bar'
17
- };
18
- expect(parser.parse(commandInfo)).toEqual({
19
- name: 'echo',
20
- command: 'npm run foo -- bar'
12
+ for (const npmCmd of ['npm', 'yarn']) {
13
+ describe(`with ${npmCmd}: prefix`, () => {
14
+ it(`expands to "${npmCmd} run <script> <args>"`, () => {
15
+ const commandInfo = {
16
+ name: 'echo',
17
+ command: `${npmCmd}:foo -- bar`
18
+ };
19
+ expect(parser.parse(commandInfo)).toEqual({
20
+ name: 'echo',
21
+ command: `${npmCmd} run foo -- bar`
22
+ });
21
23
  });
22
- });
23
24
 
24
- it('sets name to script name if none', () => {
25
- const commandInfo = {
26
- command: 'npm:foo -- bar'
27
- };
28
- expect(parser.parse(commandInfo)).toEqual({
29
- name: 'foo',
30
- command: 'npm run foo -- bar'
25
+ it('sets name to script name if none', () => {
26
+ const commandInfo = {
27
+ command: `${npmCmd}:foo -- bar`
28
+ };
29
+ expect(parser.parse(commandInfo)).toEqual({
30
+ name: 'foo',
31
+ command: `${npmCmd} run foo -- bar`
32
+ });
31
33
  });
32
34
  });
33
- });
35
+
36
+ }
@@ -7,7 +7,7 @@ module.exports = class ExpandNpmWildcard {
7
7
  }
8
8
 
9
9
  parse(commandInfo) {
10
- const [, cmdName, args] = commandInfo.command.match(/npm run (\S+)([^&]*)/) || [];
10
+ const [, npmCmd, cmdName, args] = commandInfo.command.match(/(npm|yarn) run (\S+)([^&]*)/) || [];
11
11
  const wildcardPosition = (cmdName || '').indexOf('*');
12
12
 
13
13
  // If the regex didn't match an npm script, or it has no wildcard,
@@ -27,7 +27,7 @@ module.exports = class ExpandNpmWildcard {
27
27
  return this.scripts
28
28
  .filter(script => wildcardRegex.test(script))
29
29
  .map(script => Object.assign({}, commandInfo, {
30
- command: `npm run ${script}${args}`,
30
+ command: `${npmCmd} run ${script}${args}`,
31
31
  name: script
32
32
  }));
33
33
  }
@@ -31,24 +31,28 @@ it('expands to nothing if no scripts exist in package.json', () => {
31
31
  expect(parser.parse({ command: 'npm run foo-*-baz qux' })).toEqual([]);
32
32
  });
33
33
 
34
- it('expands to all scripts matching pattern', () => {
35
- readPkg.mockReturnValue({
36
- scripts: {
37
- 'foo-bar-baz': '',
38
- 'foo--baz': '',
39
- }
34
+ for (const npmCmd of ['npm', 'yarn']) {
35
+ describe(`with an ${npmCmd}: prefix`, () => {
36
+ it('expands to all scripts matching pattern', () => {
37
+ readPkg.mockReturnValue({
38
+ scripts: {
39
+ 'foo-bar-baz': '',
40
+ 'foo--baz': '',
41
+ }
42
+ });
43
+
44
+ expect(parser.parse({ command: `${npmCmd} run foo-*-baz qux` })).toEqual([
45
+ { name: 'foo-bar-baz', command: `${npmCmd} run foo-bar-baz qux` },
46
+ { name: 'foo--baz', command: `${npmCmd} run foo--baz qux` },
47
+ ]);
48
+ });
49
+
50
+ it('caches scripts upon calls', () => {
51
+ readPkg.mockReturnValue({});
52
+ parser.parse({ command: `${npmCmd} run foo-*-baz qux` });
53
+ parser.parse({ command: `${npmCmd} run foo-*-baz qux` });
54
+
55
+ expect(readPkg).toHaveBeenCalledTimes(1);
56
+ });
40
57
  });
41
-
42
- expect(parser.parse({ command: 'npm run foo-*-baz qux' })).toEqual([
43
- { name: 'foo-bar-baz', command: 'npm run foo-bar-baz qux' },
44
- { name: 'foo--baz', command: 'npm run foo--baz qux' },
45
- ]);
46
- });
47
-
48
- it('caches scripts upon calls', () => {
49
- readPkg.mockReturnValue({});
50
- parser.parse({ command: 'npm run foo-*-baz qux' });
51
- parser.parse({ command: 'npm run foo-*-baz qux' });
52
-
53
- expect(readPkg).toHaveBeenCalledTimes(1);
54
- });
58
+ }
@@ -3,7 +3,7 @@ module.exports = class StripQuotes {
3
3
  let { command } = commandInfo;
4
4
 
5
5
  // Removes the quotes surrounding a command.
6
- if (command[0] === '"' || command[0] === '\'') {
6
+ if (/^"(.+?)"$/.test(command) || /^'(.+?)'$/.test(command)) {
7
7
  command = command.substr(1, command.length - 2);
8
8
  }
9
9
 
@@ -12,3 +12,9 @@ it('strips single quotes', () => {
12
12
  it('strips double quotes', () => {
13
13
  expect(parser.parse({ command: '"echo foo"' })).toEqual({ command: 'echo foo' });
14
14
  });
15
+
16
+ it('does not remove quotes if they are impaired', () => {
17
+ expect(parser.parse({ command: '"echo foo' })).toEqual({ command: '"echo foo' });
18
+ expect(parser.parse({ command: 'echo foo\'' })).toEqual({ command: 'echo foo\'' });
19
+ expect(parser.parse({ command: '"echo foo\'' })).toEqual({ command: '"echo foo\'' });
20
+ });
package/src/defaults.js CHANGED
@@ -22,6 +22,6 @@ module.exports = {
22
22
  restartDelay: 0,
23
23
  // Condition of success for concurrently itself.
24
24
  success: 'all',
25
- // Refer to https://date-fns.org/v1.29.0/docs/format
26
- timestampFormat: 'YYYY-MM-DD HH:mm:ss.SSS'
25
+ // Refer to https://date-fns.org/v2.0.1/docs/format
26
+ timestampFormat: 'yyyy-MM-dd HH:mm:ss.SSS'
27
27
  };
@@ -8,7 +8,7 @@ module.exports = class KillOnSignal {
8
8
 
9
9
  handle(commands) {
10
10
  let caughtSignal;
11
- ['SIGINT', 'SIGTERM'].forEach(signal => {
11
+ ['SIGINT', 'SIGTERM', 'SIGHUP'].forEach(signal => {
12
12
  this.process.on(signal, () => {
13
13
  caughtSignal = signal;
14
14
  commands.forEach(command => command.kill(signal));
@@ -13,6 +13,16 @@ beforeEach(() => {
13
13
  controller = new KillOnSignal({ process });
14
14
  });
15
15
 
16
+ it('returns commands that keep non-close streams from original commands', () => {
17
+ const newCommands = controller.handle(commands);
18
+ newCommands.forEach((newCommand, i) => {
19
+ expect(newCommand.close).not.toBe(commands[i].close);
20
+ expect(newCommand.error).toBe(commands[i].error);
21
+ expect(newCommand.stdout).toBe(commands[i].stdout);
22
+ expect(newCommand.stderr).toBe(commands[i].stderr);
23
+ });
24
+ });
25
+
16
26
  it('returns commands that map SIGINT to exit code 0', () => {
17
27
  const newCommands = controller.handle(commands);
18
28
  expect(newCommands).not.toBe(commands);
@@ -29,6 +39,18 @@ it('returns commands that map SIGINT to exit code 0', () => {
29
39
  expect(callback).toHaveBeenCalledWith(0);
30
40
  });
31
41
 
42
+ it('returns commands that keep non-SIGINT exit codes', () => {
43
+ const newCommands = controller.handle(commands);
44
+ expect(newCommands).not.toBe(commands);
45
+ expect(newCommands).toHaveLength(commands.length);
46
+
47
+ const callback = jest.fn();
48
+ newCommands[0].close.subscribe(callback);
49
+ commands[0].close.next(1);
50
+
51
+ expect(callback).toHaveBeenCalledWith(1);
52
+ });
53
+
32
54
  it('kills all commands on SIGINT', () => {
33
55
  controller.handle(commands);
34
56
  process.emit('SIGINT');
@@ -46,3 +68,12 @@ it('kills all commands on SIGTERM', () => {
46
68
  expect(commands[0].kill).toHaveBeenCalledWith('SIGTERM');
47
69
  expect(commands[1].kill).toHaveBeenCalledWith('SIGTERM');
48
70
  });
71
+
72
+ it('kills all commands on SIGHUP', () => {
73
+ controller.handle(commands);
74
+ process.emit('SIGHUP');
75
+
76
+ expect(process.listenerCount('SIGHUP')).toBe(1);
77
+ expect(commands[0].kill).toHaveBeenCalledWith('SIGHUP');
78
+ expect(commands[1].kill).toHaveBeenCalledWith('SIGHUP');
79
+ });
@@ -116,4 +116,14 @@ describe('returned commands', () => {
116
116
  // 1 failure from commands[0], 1 success from commands[1]
117
117
  expect(callback).toHaveBeenCalledTimes(2);
118
118
  });
119
+
120
+ it('keep non-close streams from original commands', () => {
121
+ const newCommands = controller.handle(commands);
122
+ newCommands.forEach((newCommand, i) => {
123
+ expect(newCommand.close).not.toBe(commands[i].close);
124
+ expect(newCommand.error).toBe(commands[i].error);
125
+ expect(newCommand.stdout).toBe(commands[i].stdout);
126
+ expect(newCommand.stderr).toBe(commands[i].stderr);
127
+ });
128
+ });
119
129
  });
@@ -111,7 +111,7 @@ describe('#logCommandText()', () => {
111
111
  });
112
112
 
113
113
  it('logs with prefixFormat set to time (with timestampFormat)', () => {
114
- const logger = createLogger({ prefixFormat: 'time', timestampFormat: 'YYYY' });
114
+ const logger = createLogger({ prefixFormat: 'time', timestampFormat: 'yyyy' });
115
115
  logger.logCommandText('foo', {});
116
116
 
117
117
  const year = new Date().getFullYear();