Skip to content

Commit

Permalink
update prefix color selector to use a generator
Browse files Browse the repository at this point in the history
  • Loading branch information
eliasm307 committed Jul 23, 2022
1 parent 7b46038 commit 5bdf259
Show file tree
Hide file tree
Showing 5 changed files with 252 additions and 132 deletions.
6 changes: 2 additions & 4 deletions README.md
Expand Up @@ -226,7 +226,6 @@ Options:
-h, --help Show help [boolean]
-v, -V, --version Show version number [boolean]
Examples:
- Output nothing more than stdout+stderr of child processes
Expand Down Expand Up @@ -320,10 +319,9 @@ concurrently can be used programmatically by using the API documented below:
- `prefix`: the prefix type to use when logging processes output.
Possible values: `index`, `pid`, `time`, `command`, `name`, `none`, or a template (eg `[{time} process: {pid}]`).
Default: the name of the process, or its index if no name is set.
- `prefixColors`: a list of colors as supported by [chalk](https://www.npmjs.com/package/chalk).
If concurrently would run more commands than there are colors, the last color is repeated.
- `prefixColors`: a list of colors as supported by [chalk](https://www.npmjs.com/package/chalk) or `auto` to automatically pick a color.
If concurrently would run more commands than there are colors, the last color is repeated, unless if the last colour value is `auto` which means following colors are automatically picked to vary.
Prefix colors specified per-command take precedence over this list.
- `colors`: let colours be selected to vary automatically where not explicitly defined
- `prefixLength`: how many characters to show when prefixing with `command`. Default: `10`
- `raw`: whether raw mode should be used, meaning strictly process output will
be logged, without any prefixes, colouring or extra stuff.
Expand Down
11 changes: 2 additions & 9 deletions bin/concurrently.ts
Expand Up @@ -125,19 +125,13 @@ const args = yargs(argsBeforeSep)
'Comma-separated list of chalk colors to use on prefixes. ' +
'If there are more commands than colors, the last color will be repeated.\n' +
'- Available modifiers: reset, bold, dim, italic, underline, inverse, hidden, strikethrough\n' +
'- Available colors: black, red, green, yellow, blue, magenta, cyan, white, gray \n' +
'or any hex values for colors, eg #23de43\n' +
'- Available colors: black, red, green, yellow, blue, magenta, cyan, white, gray, \n' +
'any hex values for colors, eg #23de43 or auto to automatically pic a color\n' +
'- Available background colors: bgBlack, bgRed, bgGreen, bgYellow, bgBlue, bgMagenta, bgCyan, bgWhite\n' +
'See https://www.npmjs.com/package/chalk for more information.',
default: defaults.prefixColors,
type: 'string',
},
color: {
describe:
'Automatically adds varying prefix colors where commands do not have a prefix color defined',
default: defaults.color,
type: 'boolean',
},
'prefix-length': {
alias: 'l',
describe:
Expand Down Expand Up @@ -218,7 +212,6 @@ concurrently(
group: args.group,
prefix: args.prefix,
prefixColors: args.prefixColors.split(','),
color: args.color,
prefixLength: args.prefixLength,
restartDelay: args.restartAfter,
restartTries: args.restartTries,
Expand Down
6 changes: 3 additions & 3 deletions src/concurrently.ts
Expand Up @@ -9,12 +9,12 @@ import { ExpandArguments } from './command-parser/expand-arguments';
import { ExpandNpmShortcut } from './command-parser/expand-npm-shortcut';
import { ExpandNpmWildcard } from './command-parser/expand-npm-wildcard';
import { StripQuotes } from './command-parser/strip-quotes';
import { PrefixColorSelector } from './prefix-color-selector';
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';

const defaults: ConcurrentlyOptions = {
spawn,
Expand Down Expand Up @@ -136,7 +136,7 @@ export function concurrently(

const options = _.defaults(baseOptions, defaults);

const prefixColorSelector = new PrefixColorSelector(options.prefixColors, options.color);
const prefixColorSelector = new PrefixColorSelector(options.prefixColors);

const commandParsers: CommandParser[] = [
new StripQuotes(),
Expand All @@ -155,7 +155,7 @@ export function concurrently(
return new Command(
{
index,
prefixColor: prefixColorSelector.getNextColor(index),
prefixColor: prefixColorSelector.getNextColor(),
...command,
},
getSpawnOpts({
Expand Down
230 changes: 174 additions & 56 deletions src/prefix-color-selector.spec.ts
@@ -1,71 +1,189 @@
import { PrefixColorSelector } from './prefix-color-selector';

it('does not produce a color if it should not', () => {
const prefixColorSelector = new PrefixColorSelector([], false);

let selectedColor = prefixColorSelector.getNextColor(0);
expect(selectedColor).toBe('');
selectedColor = prefixColorSelector.getNextColor(1);
expect(selectedColor).toBe('');
selectedColor = prefixColorSelector.getNextColor(2);
expect(selectedColor).toBe('');
});
function assertSelectedColors({
prefixColorSelector,
expectedColors,
}: {
prefixColorSelector: PrefixColorSelector;
expectedColors: string[];
}) {
expectedColors.forEach(expectedColor => {
expect(prefixColorSelector.getNextColor()).toBe(expectedColor);
});
}

it('uses user defined prefix colors only if not allowed to use auto colors', () => {
const prefixColorSelector = new PrefixColorSelector(['red', 'green', 'blue'], false);

let selectedColor = prefixColorSelector.getNextColor(0);
expect(selectedColor).toBe('red');
selectedColor = prefixColorSelector.getNextColor(1);
expect(selectedColor).toBe('green');
selectedColor = prefixColorSelector.getNextColor(2);
expect(selectedColor).toBe('blue');

// uses last color if no more user defined colors
selectedColor = prefixColorSelector.getNextColor(3);
expect(selectedColor).toBe('blue');
selectedColor = prefixColorSelector.getNextColor(4);
expect(selectedColor).toBe('blue');
afterEach(() => {
jest.restoreAllMocks();
});

it('uses user defined colors then recurring auto colors without repeating consecutive colors', () => {
const prefixColorSelector = new PrefixColorSelector(['red', 'green'], true);
describe('#getNextColor', function () {
it('does not produce a color if prefixColors empty', () => {
assertSelectedColors({
prefixColorSelector: new PrefixColorSelector([]),
expectedColors: ['', '', ''],
});
});

jest.spyOn(prefixColorSelector, 'ACCEPTABLE_CONSOLE_COLORS', 'get').mockReturnValue([
'green',
'blue',
]);
it('does not produce a color if prefixColors undefined', () => {
assertSelectedColors({
prefixColorSelector: new PrefixColorSelector(),
expectedColors: ['', '', ''],
});
});

let selectedColor = prefixColorSelector.getNextColor(0);
expect(selectedColor).toBe('red');
selectedColor = prefixColorSelector.getNextColor(1);
expect(selectedColor).toBe('green');
it('uses user defined prefix colors only, if no auto is used', () => {
assertSelectedColors({
prefixColorSelector: new PrefixColorSelector(['red', 'green', 'blue']),
expectedColors: [
'red',
'green',
'blue',

// auto colors now, does not repeat last user color of green
selectedColor = prefixColorSelector.getNextColor(2);
expect(selectedColor).toBe('blue');
// uses last color if last color is not "auto"
'blue',
'blue',
'blue',
],
});
});

selectedColor = prefixColorSelector.getNextColor(3);
expect(selectedColor).toBe('green');
it('uses picks colors when user defines an auto color', () => {
jest.spyOn(PrefixColorSelector, 'ACCEPTABLE_CONSOLE_COLORS', 'get').mockReturnValue([
'green',
'blue',
]);

selectedColor = prefixColorSelector.getNextColor(4);
expect(selectedColor).toBe('blue');
});
assertSelectedColors({
prefixColorSelector: new PrefixColorSelector(['red', 'green', 'auto', 'orange']),
expectedColors: [
// custom colors
'red',
'green',
'blue', // picks auto color, not repeating consecutive "green" color
'orange',

it('has more than 1 auto color defined', () => {
const prefixColorSelector = new PrefixColorSelector([], true);
// ! code assumes this always has more than one entry, so make sure
expect(prefixColorSelector.ACCEPTABLE_CONSOLE_COLORS.length).toBeGreaterThan(1);
});
// uses last color if last color is not "auto"
'orange',
'orange',
'orange',
],
});
});

it('can use only auto colors and does not repeat consecutive colors', () => {
const prefixColorSelector = new PrefixColorSelector([], true);
it('uses user defined colors then recurring auto colors without repeating consecutive colors', () => {
jest.spyOn(PrefixColorSelector, 'ACCEPTABLE_CONSOLE_COLORS', 'get').mockReturnValue([
'green',
'blue',
]);

assertSelectedColors({
prefixColorSelector: new PrefixColorSelector(['red', 'green', 'auto']),
expectedColors: [
// custom colors
'red',
'green',

// picks auto colors, not repeating consecutive "green" color
'blue',
'green',
'blue',
'green',
],
});
});

it('can sometimes produce consecutive colors', () => {
jest.spyOn(PrefixColorSelector, 'ACCEPTABLE_CONSOLE_COLORS', 'get').mockReturnValue([
'green',
'blue',
]);

assertSelectedColors({
prefixColorSelector: new PrefixColorSelector(['blue', 'auto']),
expectedColors: [
// custom colors
'blue',

// picks auto colors
'green',
// does not repeat custom colors for initial auto colors, ie does not use "blue" again so soon
'green', // consecutive color picked, however practically there would be a lot of colors that need to be set in a particular order for this to occur
'blue',
'green',
'blue',
'green',
'blue',
],
});
});

it('considers the Bright variants of colors equal to the normal colors to avoid similar colors', function () {
jest.spyOn(PrefixColorSelector, 'ACCEPTABLE_CONSOLE_COLORS', 'get').mockReturnValue([
'greenBright',
'blueBright',
'green',
'blue',
'magenta',
]);

assertSelectedColors({
prefixColorSelector: new PrefixColorSelector(['green', 'blue', 'auto']),
expectedColors: [
// custom colors
'green',
'blue',

// picks auto colors, not repeating green and blue colors and variants initially
'magenta',

// picks auto colors
'greenBright',
'blueBright',
'green',
'blue',
'magenta',
],
});
});

it('does not repeat consecutive colors when last prefixColor is auto', () => {
const prefixColorSelector = new PrefixColorSelector(['auto']);

// pick auto colors over 2 sets
const expectedColors: string[] = [
...PrefixColorSelector.ACCEPTABLE_CONSOLE_COLORS,
...PrefixColorSelector.ACCEPTABLE_CONSOLE_COLORS,
];

expectedColors.reduce((previousColor, currentExpectedColor) => {
const actualSelectedColor = prefixColorSelector.getNextColor();
expect(actualSelectedColor).not.toBe(previousColor); // no consecutive colors
expect(actualSelectedColor).toBe(currentExpectedColor); // expected color
return actualSelectedColor;
}, '');
});

it('handles when more individual auto prefixColors exist than acceptable console colors', () => {
// pick auto colors over 2 sets
const expectedColors: string[] = [
...PrefixColorSelector.ACCEPTABLE_CONSOLE_COLORS,
...PrefixColorSelector.ACCEPTABLE_CONSOLE_COLORS,
];

const prefixColorSelector = new PrefixColorSelector(expectedColors.map(() => 'auto'));

expectedColors.reduce((previousColor, currentExpectedColor) => {
const actualSelectedColor = prefixColorSelector.getNextColor();
expect(actualSelectedColor).not.toBe(previousColor); // no consecutive colors
expect(actualSelectedColor).toBe(currentExpectedColor); // expected color
return actualSelectedColor;
}, '');
});
});

let previousColor;
let selectedColor: string;
Array(prefixColorSelector.ACCEPTABLE_CONSOLE_COLORS.length * 2).forEach((_, index) => {
previousColor = selectedColor;
selectedColor = prefixColorSelector.getNextColor(index);
expect(selectedColor).not.toBe(previousColor);
describe('PrefixColorSelector#ACCEPTABLE_CONSOLE_COLORS', () => {
it('has more than 1 auto color defined', () => {
// ! code assumes this always has more than one entry, so make sure
expect(PrefixColorSelector.ACCEPTABLE_CONSOLE_COLORS.length).toBeGreaterThan(1);
});
});

0 comments on commit 5bdf259

Please sign in to comment.