From 04b7d4abf2758808ece01140bc1ad2cb1a29f6a4 Mon Sep 17 00:00:00 2001 From: Gustavo Henke Date: Thu, 6 Jun 2024 00:41:53 +1000 Subject: [PATCH] Bring spawn-command into concurrently (#479) --- README.md | 1 - declarations/spawn-command.d.ts | 6 ---- package.json | 1 - pnpm-lock.yaml | 9 +---- src/concurrently.ts | 5 ++- src/get-spawn-opts.spec.ts | 34 ------------------- src/spawn.spec.ts | 52 +++++++++++++++++++++++++++++ src/{get-spawn-opts.ts => spawn.ts} | 23 ++++++++++++- 8 files changed, 77 insertions(+), 54 deletions(-) delete mode 100644 declarations/spawn-command.d.ts delete mode 100644 src/get-spawn-opts.spec.ts create mode 100644 src/spawn.spec.ts rename src/{get-spawn-opts.ts => spawn.ts} (61%) diff --git a/README.md b/README.md index 7f1ad32d..a9edd791 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,6 @@ tired of opening terminals and made **concurrently**. - Cross platform (including Windows) - Output is easy to follow with prefixes - With `--kill-others` switch, all commands are killed if one dies -- Spawns commands with [spawn-command](https://github.com/mmalecki/spawn-command) ## Installation diff --git a/declarations/spawn-command.d.ts b/declarations/spawn-command.d.ts deleted file mode 100644 index 4f1cd5fe..00000000 --- a/declarations/spawn-command.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -declare module 'spawn-command' { - import { ChildProcess, SpawnOptions } from 'child_process'; - - function spawnCommand(command: string, options: SpawnOptions): ChildProcess; - export = spawnCommand; -} diff --git a/package.json b/package.json index 6ec63e19..e1b6a5d0 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "lodash": "^4.17.21", "rxjs": "^7.8.1", "shell-quote": "^1.8.1", - "spawn-command": "0.0.2-1", "supports-color": "^8.1.1", "tree-kill": "^1.2.2", "yargs": "^17.7.2" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b0a019a0..f49535d3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.1' +lockfileVersion: '6.0' settings: autoInstallPeers: false @@ -20,9 +20,6 @@ dependencies: shell-quote: specifier: ^1.8.1 version: 1.8.1 - spawn-command: - specifier: 0.0.2-1 - version: 0.0.2-1 supports-color: specifier: ^8.1.1 version: 8.1.1 @@ -4386,10 +4383,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /spawn-command@0.0.2-1: - resolution: {integrity: sha512-n98l9E2RMSJ9ON1AKisHzz7V42VDiBQGY6PB1BwRglz99wpVsSuGzQ+jOi6lFXBGVTCrRpltvjm+/XA+tpeJrg==} - dev: false - /sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} dev: true diff --git a/src/concurrently.ts b/src/concurrently.ts index e995939d..06f585f9 100644 --- a/src/concurrently.ts +++ b/src/concurrently.ts @@ -1,7 +1,6 @@ import assert from 'assert'; import _ from 'lodash'; import { cpus } from 'os'; -import spawn from 'spawn-command'; import { Writable } from 'stream'; import treeKill from 'tree-kill'; @@ -13,10 +12,10 @@ import { ExpandNpmWildcard } from './command-parser/expand-npm-wildcard'; import { StripQuotes } from './command-parser/strip-quotes'; import { CompletionListener, SuccessCondition } from './completion-listener'; import { FlowController } from './flow-control/flow-controller'; -import { getSpawnOpts } from './get-spawn-opts'; import { Logger } from './logger'; import { OutputWriter } from './output-writer'; import { PrefixColorSelector } from './prefix-color-selector'; +import { getSpawnOpts, spawn } from './spawn'; const defaults: ConcurrentlyOptions = { spawn, @@ -119,7 +118,7 @@ export type ConcurrentlyOptions = { /** * A function that will spawn commands. - * Defaults to the `spawn-command` module. + * Defaults to a function that spawns using either `cmd.exe` or `/bin/sh`. */ spawn: SpawnCommand; diff --git a/src/get-spawn-opts.spec.ts b/src/get-spawn-opts.spec.ts deleted file mode 100644 index 935ddd85..00000000 --- a/src/get-spawn-opts.spec.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { getSpawnOpts } from './get-spawn-opts'; - -const baseProcess = { - platform: 'win32' as const, - cwd: () => '', - env: {}, -}; - -it('sets detached mode to false for Windows platform', () => { - expect(getSpawnOpts({ process: baseProcess }).detached).toBe(false); -}); - -it('sets stdio to inherit when raw', () => { - expect(getSpawnOpts({ raw: true }).stdio).toBe('inherit'); -}); - -it('merges FORCE_COLOR into env vars if color supported', () => { - const process = { ...baseProcess, env: { foo: 'bar' } }; - 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 = { ...baseProcess, cwd: () => 'process-cwd' }; - expect(getSpawnOpts({ process }).cwd).toBe('process-cwd'); -}); - -it('overrides default cwd', () => { - const cwd = 'foobar'; - expect(getSpawnOpts({ cwd }).cwd).toBe(cwd); -}); diff --git a/src/spawn.spec.ts b/src/spawn.spec.ts new file mode 100644 index 00000000..9fb7ec88 --- /dev/null +++ b/src/spawn.spec.ts @@ -0,0 +1,52 @@ +import { getSpawnOpts, spawn } from './spawn'; + +const baseProcess = { + platform: 'win32' as const, + cwd: () => '', + env: {}, +}; + +describe('spawn()', () => { + it('spawns the given command', async () => { + const fakeSpawn = jest.fn(); + spawn('echo banana', {}, fakeSpawn, baseProcess); + expect(fakeSpawn).toHaveBeenCalled(); + expect(fakeSpawn.mock.calls[0][1].join(' ')).toContain('echo banana'); + }); + + it('returns spawned process', async () => { + const childProcess = {}; + const fakeSpawn = jest.fn().mockReturnValue(childProcess); + const child = spawn('echo banana', {}, fakeSpawn, baseProcess); + expect(child).toBe(childProcess); + }); +}); + +describe('getSpawnOpts()', () => { + it('sets detached mode to false for Windows platform', () => { + expect(getSpawnOpts({ process: baseProcess }).detached).toBe(false); + }); + + it('sets stdio to inherit when raw', () => { + expect(getSpawnOpts({ raw: true }).stdio).toBe('inherit'); + }); + + it('merges FORCE_COLOR into env vars if color supported', () => { + const process = { ...baseProcess, env: { foo: 'bar' } }; + 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 = { ...baseProcess, cwd: () => 'process-cwd' }; + expect(getSpawnOpts({ process }).cwd).toBe('process-cwd'); + }); + + it('overrides default cwd', () => { + const cwd = 'foobar'; + expect(getSpawnOpts({ cwd }).cwd).toBe(cwd); + }); +}); diff --git a/src/get-spawn-opts.ts b/src/spawn.ts similarity index 61% rename from src/get-spawn-opts.ts rename to src/spawn.ts index a65d6faa..510d46de 100644 --- a/src/get-spawn-opts.ts +++ b/src/spawn.ts @@ -1,6 +1,27 @@ -import { SpawnOptions } from 'child_process'; +import { ChildProcess, spawn as baseSpawn, SpawnOptions } from 'child_process'; import supportsColor from 'supports-color'; +/** + * Spawns a command using `cmd.exe` on Windows, or `/bin/sh` elsewhere. + */ +// Implementation based off of https://github.com/mmalecki/spawn-command/blob/v0.0.2-1/lib/spawn-command.js +export function spawn( + command: string, + options: SpawnOptions, + // For testing + spawn: (command: string, args: string[], options: SpawnOptions) => ChildProcess = baseSpawn, + process: Pick = global.process, +): ChildProcess { + let file = '/bin/sh'; + let args = ['-c', command]; + if (process.platform === 'win32') { + file = 'cmd.exe'; + args = ['/s', '/c', `"${command}"`]; + options.windowsVerbatimArguments = true; + } + return spawn(file, args, options); +} + export const getSpawnOpts = ({ colorSupport = supportsColor.stdout, cwd,