diff --git a/README.md b/README.md index 8abb872b..6cef9a93 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,13 @@ Like `npm run watch-js & npm run watch-less` but better. ![](docs/demo.gif) **Table of contents** -- [Why](#why) -- [Install](#install) -- [Usage](#usage) -- [Programmatic Usage](#programmatic-usage) -- [FAQ](#faq) +- [Concurrently](#concurrently) + - [Why](#why) + - [Install](#install) + - [Usage](#usage) + - [Programmatic Usage](#programmatic-usage) + - [`concurrently(commands[, options])`](#concurrentlycommands-options) + - [FAQ](#faq) ## Why @@ -226,8 +228,10 @@ concurrently can be used programmatically by using the API documented below: ### `concurrently(commands[, options])` - `commands`: an array of either strings (containing the commands to run) or objects - with the shape `{ command, name, prefixColor, env }`. + with the shape `{ command, name, prefixColor, env, cwd }`. - `options` (optional): an object containing any of the below: + - `cwd`: the working directory to be used by all commands. Can be overriden per command. + Default: `process.cwd()`. - `defaultInputTarget`: the default input target when reading from `inputStream`. Default: `0`. - `inputStream`: a [`Readable` stream](https://nodejs.org/dist/latest-v10.x/docs/api/stream.html#stream_readable_streams) @@ -264,11 +268,13 @@ const concurrently = require('concurrently'); concurrently([ 'npm:watch-*', { command: 'nodemon', name: 'server' }, - { command: 'deploy', name: 'deploy', env: { PUBLIC_KEY: '...' } } + { command: 'deploy', name: 'deploy', env: { PUBLIC_KEY: '...' } }, + { command: 'watch', name: 'watch', cwd: path.resolve(__dirname, 'scripts/watchers')} ], { prefix: 'name', killOthers: ['failure', 'success'], restartTries: 3, + cwd: path.resolve(__dirname, 'scripts'), }).then(success, failure); ``` diff --git a/index.js b/index.js index 82fb0b5d..26f4661f 100644 --- a/index.js +++ b/index.js @@ -22,6 +22,7 @@ module.exports = (commands, options = {}) => { maxProcesses: options.maxProcesses, raw: options.raw, successCondition: options.successCondition, + cwd: options.cwd, controllers: [ new LogError({ logger }), new LogOutput({ logger }), diff --git a/src/concurrently.js b/src/concurrently.js index 6d466e23..8ee028bb 100644 --- a/src/concurrently.js +++ b/src/concurrently.js @@ -16,7 +16,8 @@ const defaults = { spawn, kill: treeKill, raw: false, - controllers: [] + controllers: [], + cwd: undefined, }; module.exports = (commands, options) => { @@ -37,7 +38,11 @@ module.exports = (commands, options) => { .map((command, index) => new Command( Object.assign({ index, - spawnOpts: getSpawnOpts({ raw: options.raw, env: command.env }), + spawnOpts: getSpawnOpts({ + raw: options.raw, + env: command.env, + cwd: command.cwd || options.cwd, + }), killProcess: options.kill, spawn: options.spawn, }, command) diff --git a/src/concurrently.spec.js b/src/concurrently.spec.js index 9de153fd..79e07cdf 100644 --- a/src/concurrently.spec.js +++ b/src/concurrently.spec.js @@ -104,15 +104,42 @@ it('merges extra env vars into each command', () => { { command: 'echo', env: { foo: 'baz' } }, 'kill' ]); - + + expect(spawn).toHaveBeenCalledTimes(3); + expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({ + env: expect.objectContaining({ foo: 'bar' }) + })); + expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({ + env: expect.objectContaining({ foo: 'baz' }) + })); + expect(spawn).toHaveBeenCalledWith('kill', expect.objectContaining({ + env: expect.not.objectContaining({ foo: expect.anything() }) + })); +}); + +it('uses cwd from options for each command', () => { + create( + [ + { command: 'echo', env: { foo: 'bar' } }, + { command: 'echo', env: { foo: 'baz' } }, + 'kill' + ], + { + cwd: 'foobar', + } + ); + expect(spawn).toHaveBeenCalledTimes(3); expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({ - env: expect.objectContaining({ foo: 'bar' }) + env: expect.objectContaining({ foo: 'bar' }), + cwd: 'foobar', })); expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({ - env: expect.objectContaining({ foo: 'baz' }) + env: expect.objectContaining({ foo: 'baz' }), + cwd: 'foobar', })); expect(spawn).toHaveBeenCalledWith('kill', expect.objectContaining({ - env: expect.not.objectContaining({ foo: expect.anything() }) + env: expect.not.objectContaining({ foo: expect.anything() }), + cwd: 'foobar', })); }); diff --git a/src/defaults.js b/src/defaults.js index 2d315e4f..a162a062 100644 --- a/src/defaults.js +++ b/src/defaults.js @@ -25,5 +25,7 @@ module.exports = { // Condition of success for concurrently itself. success: 'all', // Refer to https://date-fns.org/v2.0.1/docs/format - timestampFormat: 'yyyy-MM-dd HH:mm:ss.SSS' + timestampFormat: 'yyyy-MM-dd HH:mm:ss.SSS', + // Current working dir passed as option to spawn command. Default: process.cwd() + cwd: undefined }; diff --git a/src/get-spawn-opts.js b/src/get-spawn-opts.js index a74f65e9..972d5e29 100644 --- a/src/get-spawn-opts.js +++ b/src/get-spawn-opts.js @@ -2,11 +2,14 @@ const supportsColor = require('supports-color'); module.exports = ({ colorSupport = supportsColor.stdout, + cwd, process = global.process, raw = false, - env = {} + env = {}, }) => Object.assign( - {}, + { + cwd: cwd || process.cwd(), + }, raw && { stdio: 'inherit' }, /^win/.test(process.platform) && { detached: false }, { env: Object.assign(colorSupport ? { FORCE_COLOR: colorSupport.level } : {}, process.env, env) } diff --git a/src/get-spawn-opts.spec.js b/src/get-spawn-opts.spec.js index 20371623..5f9412c2 100644 --- a/src/get-spawn-opts.spec.js +++ b/src/get-spawn-opts.spec.js @@ -1,7 +1,7 @@ const getSpawnOpts = require('./get-spawn-opts'); it('sets detached mode to false for Windows platform', () => { - expect(getSpawnOpts({ process: { platform: 'win32' } }).detached).toBe(false); + expect(getSpawnOpts({ process: { platform: 'win32', cwd: jest.fn() } }).detached).toBe(false); }); it('sets stdio to inherit when raw', () => { @@ -9,10 +9,22 @@ it('sets stdio to inherit when raw', () => { }); it('merges FORCE_COLOR into env vars if color supported', () => { - const process = { env: { foo: 'bar' } }; + const process = { env: { foo: 'bar' }, cwd: jest.fn() }; expect(getSpawnOpts({ process, colorSupport: false }).env).toEqual(process.env); expect(getSpawnOpts({ process, colorSupport: { level: 1 } }).env).toEqual({ FORCE_COLOR: 1, foo: 'bar' }); }); + +it('sets default cwd to process.cwd()', () => { + const process = { cwd: jest.fn().mockReturnValue('process-cwd') }; + expect(getSpawnOpts({ + process, + }).cwd).toBe('process-cwd'); +}); + +it('overrides default cwd', () => { + const cwd = 'foobar'; + expect(getSpawnOpts({ cwd }).cwd).toBe(cwd); +});