Skip to content

Commit

Permalink
Combine flags related to stdio
Browse files Browse the repository at this point in the history
Follow-up from #486
  • Loading branch information
gustavohenke committed Jul 20, 2024
1 parent e52d984 commit 3b2fadb
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 40 deletions.
45 changes: 43 additions & 2 deletions src/concurrently.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,8 +291,8 @@ it('uses overridden raw option for each command if specified', () => {
expect(spawn).toHaveBeenCalledTimes(2);
expect(spawn).toHaveBeenCalledWith(
'echo',
expect.not.objectContaining({
stdio: expect.anything(),
expect.objectContaining({
stdio: 'pipe',
}),
);
expect(spawn).toHaveBeenCalledWith(
Expand All @@ -303,6 +303,47 @@ it('uses overridden raw option for each command if specified', () => {
);
});

it('uses hide from options for each command', () => {
create([{ command: 'echo' }, 'kill'], {
hide: [1],
});

expect(spawn).toHaveBeenCalledTimes(2);
expect(spawn).toHaveBeenCalledWith(
'echo',
expect.objectContaining({
stdio: 'pipe',
}),
);
expect(spawn).toHaveBeenCalledWith(
'kill',
expect.objectContaining({
stdio: ['ignore', 'ignore', 'pipe'],
}),
);
});

it('hides output for commands even if raw option is on', () => {
create([{ command: 'echo' }, 'kill'], {
hide: [1],
raw: true,
});

expect(spawn).toHaveBeenCalledTimes(2);
expect(spawn).toHaveBeenCalledWith(
'echo',
expect.objectContaining({
stdio: 'inherit',
}),
);
expect(spawn).toHaveBeenCalledWith(
'kill',
expect.objectContaining({
stdio: ['ignore', 'ignore', 'pipe'],
}),
);
});

it('argument placeholders are properly replaced when additional arguments are passed', () => {
create(
[
Expand Down
16 changes: 5 additions & 11 deletions src/concurrently.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,9 @@ export type ConcurrentlyOptions = {
raw?: boolean;

/**
* Which command(s) should have their output hidden.
* Which commands should have their output hidden.
*/
hide?: CommandIdentifier | CommandIdentifier[];
hide?: CommandIdentifier[];

/**
* The current working directory of commands which didn't specify one.
Expand Down Expand Up @@ -181,28 +181,22 @@ export function concurrently(
commandParsers.push(new ExpandArguments(options.additionalArguments));
}

// To avoid empty strings from hiding the output of commands that don't have a name,
// keep in the list of commands to hide only strings with some length.
// This might happen through the CLI when no `--hide` argument is specified, for example.
const hide = _.castArray(options.hide)
.filter((name) => name || name === 0)
.map(String);

const hide = (options.hide || []).map(String);
let commands = _(baseCommands)
.map(mapToCommandInfo)
.flatMap((command) => parseCommand(command, commandParsers))
.map((command, index) => {
const hidden = hide.includes(command.name) || hide.includes(String(index));
return new Command(
{
index,
prefixColor: prefixColorSelector.getNextColor(),
...command,
},
getSpawnOpts({
raw: command.raw ?? options.raw,
stdio: hidden ? 'hidden' : command.raw ?? options.raw ? 'raw' : 'normal',
env: command.env,
cwd: command.cwd || options.cwd,
hide: hide.includes(String(index)) || hide.includes(command.name),
}),
options.spawn,
options.kill,
Expand Down
11 changes: 8 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import _ from 'lodash';
import { Readable } from 'stream';

import { CloseEvent, Command, CommandIdentifier, TimerEvent } from './command';
Expand All @@ -18,7 +19,7 @@ import { LogTimings } from './flow-control/log-timings';
import { RestartDelay, RestartProcess } from './flow-control/restart-process';
import { Logger } from './logger';

export type ConcurrentlyOptions = Omit<BaseConcurrentlyOptions, 'abortSignal'> & {
export type ConcurrentlyOptions = Omit<BaseConcurrentlyOptions, 'abortSignal' | 'hide'> & {
// Logger options
/**
* Which command(s) should have their output hidden.
Expand Down Expand Up @@ -95,8 +96,12 @@ export function concurrently(
commands: ConcurrentlyCommandInput[],
options: Partial<ConcurrentlyOptions> = {},
) {
// To avoid empty strings from hiding the output of commands that don't have a name,
// keep in the list of commands to hide only strings with some length.
// This might happen through the CLI when no `--hide` argument is specified, for example.
const hide = _.castArray(options.hide).filter((id) => id || id === 0);
const logger = new Logger({
hide: options.hide,
hide,
prefixFormat: options.prefix,
prefixLength: options.prefixLength,
raw: options.raw,
Expand All @@ -110,7 +115,7 @@ export function concurrently(
raw: options.raw,
successCondition: options.successCondition,
cwd: options.cwd,
hide: options.hide,
hide,
logger,
outputStream: options.outputStream || process.stdout,
group: options.group,
Expand Down
11 changes: 3 additions & 8 deletions src/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ export class Logger {
timestampFormat,
}: {
/**
* Which command(s) should have their output hidden.
* Which commands should have their output hidden.
*/
hide?: CommandIdentifier | CommandIdentifier[];
hide?: CommandIdentifier[];

/**
* Whether output should be formatted to include prefixes and whether "event" logs will be
Expand All @@ -60,12 +60,7 @@ export class Logger {
*/
timestampFormat?: string;
}) {
// To avoid empty strings from hiding the output of commands that don't have a name,
// keep in the list of commands to hide only strings with some length.
// This might happen through the CLI when no `--hide` argument is specified, for example.
this.hide = _.castArray(hide)
.filter((name) => name || name === 0)
.map(String);
this.hide = (hide || []).map(String);
this.raw = raw;
this.prefixFormat = prefixFormat;
this.prefixLength = prefixLength || defaults.prefixLength;
Expand Down
12 changes: 8 additions & 4 deletions src/spawn.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,16 @@ describe('getSpawnOpts()', () => {
expect(getSpawnOpts({ process: baseProcess }).detached).toBe(false);
});

it('sets stdio to inherit when raw', () => {
expect(getSpawnOpts({ raw: true }).stdio).toBe('inherit');
it('sets stdio to pipe when stdio mode is normal', () => {
expect(getSpawnOpts({ stdio: 'normal' }).stdio).toBe('pipe');
});

it('unsets stdio when raw and hide', () => {
expect(getSpawnOpts({ raw: true, hide: true }).stdio).toBeUndefined();
it('sets stdio to inherit when stdio mode is raw', () => {
expect(getSpawnOpts({ stdio: 'raw' }).stdio).toBe('inherit');
});

it('sets stdio to ignore stdout + stderr when stdio mode is hidden', () => {
expect(getSpawnOpts({ stdio: 'hidden' }).stdio).toEqual(['ignore', 'ignore', 'pipe']);
});

it('merges FORCE_COLOR into env vars if color supported', () => {
Expand Down
22 changes: 10 additions & 12 deletions src/spawn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,8 @@ export const getSpawnOpts = ({
colorSupport = supportsColor.stdout,
cwd,
process = global.process,
raw = false,
stdio = 'normal',
env = {},
hide = false,
}: {
/**
* What the color support of the spawned processes should be.
Expand All @@ -50,24 +49,23 @@ export const getSpawnOpts = ({
cwd?: string;

/**
* Whether to customize the options for spawning processes in raw mode.
* Defaults to false.
* Which stdio mode to use. Raw implies inheriting the parent process' stdio.
*
* - `normal`: all of stdout, stderr and stdin are piped
* - `hidden`: stdin is piped, stdout/stderr outputs are ignored
* - `raw`: all of stdout, stderr and stdin are inherited from the main process
*
* Defaults to `normal`.
*/
raw?: boolean;
stdio?: 'normal' | 'hidden' | 'raw';

/**
* Map of custom environment variables to include in the spawn options.
*/
env?: Record<string, unknown>;

/**
* Whether to hide the standard output.
* Defaults to false.
*/
hide?: boolean;
}): SpawnOptions => ({
cwd: cwd || process.cwd(),
...(raw && !hide && { stdio: 'inherit' as const }),
stdio: stdio === 'normal' ? 'pipe' : stdio === 'raw' ? 'inherit' : ['ignore', 'ignore', 'pipe'],
...(/^win/.test(process.platform) && { detached: false }),
env: {
...(colorSupport ? { FORCE_COLOR: colorSupport.level.toString() } : {}),
Expand Down

0 comments on commit 3b2fadb

Please sign in to comment.