diff --git a/docs/api/cautious-journey.syncoptions.colors.md b/docs/api/cautious-journey.syncoptions.colors.md new file mode 100644 index 0000000..b812f3c --- /dev/null +++ b/docs/api/cautious-journey.syncoptions.colors.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [cautious-journey](./cautious-journey.md) > [SyncOptions](./cautious-journey.syncoptions.md) > [colors](./cautious-journey.syncoptions.colors.md) + +## SyncOptions.colors property + +Signature: + +```typescript +colors: Array; +``` diff --git a/docs/api/cautious-journey.syncoptions.md b/docs/api/cautious-journey.syncoptions.md index d6d7900..2817b9b 100644 --- a/docs/api/cautious-journey.syncoptions.md +++ b/docs/api/cautious-journey.syncoptions.md @@ -14,6 +14,7 @@ export interface SyncOptions | Property | Type | Description | | --- | --- | --- | +| [colors](./cautious-journey.syncoptions.colors.md) | Array<string> | | | [flags](./cautious-journey.syncoptions.flags.md) | Array<[FlagLabel](./cautious-journey.flaglabel.md)> | | | [logger](./cautious-journey.syncoptions.logger.md) | Logger | | | [project](./cautious-journey.syncoptions.project.md) | string | | diff --git a/src/labels.ts b/src/labels.ts index a8a9a03..2154cb7 100644 --- a/src/labels.ts +++ b/src/labels.ts @@ -1,3 +1,6 @@ +import { doesExist, InvalidArgumentError } from '@apextoaster/js-utils'; +import { randomItem } from './utils'; + /** * A reference to another label. */ @@ -113,3 +116,32 @@ export function prioritizeLabels(labels: Array } }); } + +/** + * Pick a label color, preferring the label data if set, falling back to a randomly selected color. + * + * TODO: this is a terrible overload + */ +export function colorizeLabel(flag: FlagLabel, colors: Array): string; +export function colorizeLabel(state: StateLabel, value: StateValue, colors: Array): string; +export function colorizeLabel(label: BaseLabel, colorsOrValue: Array | StateValue, maybeColors?: Array): string { + if (Array.isArray(colorsOrValue)) { + const { color } = label; + if (doesExist(color)) { + return color; + } else { + return randomItem(colorsOrValue); + } + } else { + if (!Array.isArray(maybeColors)) { + throw new InvalidArgumentError(); + } + + const { color = colorsOrValue.color } = label; + if (doesExist(color)) { + return color; + } else { + return randomItem(maybeColors); + } + } +} diff --git a/src/main.ts b/src/main.ts index c870c1f..0d7d560 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,4 +1,4 @@ -import { InvalidArgumentError, isNil, doesExist } from '@apextoaster/js-utils'; +import { doesExist, InvalidArgumentError, isNil } from '@apextoaster/js-utils'; import { createSchema } from '@apextoaster/js-yaml-schema'; import { existsSync, readFileSync, realpathSync } from 'fs'; import { DEFAULT_SAFE_SCHEMA, safeLoad } from 'js-yaml'; @@ -57,25 +57,29 @@ export async function main(argv: Array): Promise { }, 'startup environment'); for (const project of config.projects) { - if (doesExist(args.project) && !args.project.includes(project.name)) { - logger.info({ project: project.name }, 'skipping project'); + const { colors, flags, name, states } = project; + + if (doesExist(args.project) && !args.project.includes(name)) { + logger.info({ project: name }, 'skipping project'); continue; } const remote = new GithubRemote({ - ...project.remote, - dryrun: args.dryrun, + data: project.remote.data, + dryrun: args.dryrun || project.remote.dryrun, logger, + type: project.remote.type, }); await remote.connect(); // mode switch const options: SyncOptions = { - flags: project.flags, + colors, + flags, logger, - project: project.name, + project: name, remote, - states: project.states, + states, }; switch (mode) { case Commands.ISSUES: @@ -85,7 +89,7 @@ export async function main(argv: Array): Promise { await syncLabels(options); break; default: - throw new InvalidArgumentError('unknown mode'); + throw new InvalidArgumentError('unknown command'); } } diff --git a/src/sync.ts b/src/sync.ts index 675dc77..6219d5c 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -1,12 +1,13 @@ import { doesExist, mustExist } from '@apextoaster/js-utils'; import { Logger } from 'noicejs'; -import { FlagLabel, getLabelNames, StateLabel, valueName } from './labels'; +import { colorizeLabel, FlagLabel, getLabelNames, StateLabel, valueName } from './labels'; import { LabelUpdate, Remote } from './remote'; import { resolveLabels } from './resolve'; -import { defaultTo } from './utils'; +import { defaultTo, defaultUntil } from './utils'; export interface SyncOptions { + colors: Array; flags: Array; logger: Logger; project: string; @@ -88,7 +89,7 @@ export async function createLabel(options: SyncOptions, name: string) { const flag = options.flags.find((it) => name === it.name); if (doesExist(flag)) { await options.remote.createLabel({ - color: mustExist(flag.color), + color: colorizeLabel(flag, options.colors), desc: mustExist(flag.desc), name, project: options.project, @@ -102,8 +103,8 @@ export async function createLabel(options: SyncOptions, name: string) { const value = state.values.find((it) => valueName(state, it) === name); if (doesExist(value)) { await options.remote.createLabel({ - color: defaultTo(defaultTo(value.color, state.color), ''), - desc: defaultTo(defaultTo(value.desc, state.desc), ''), + color: colorizeLabel(state, value, options.colors), + desc: defaultUntil(value.desc, state.desc, ''), name: valueName(state, value), project: options.project, }); @@ -138,7 +139,7 @@ export async function syncSingleLabel(options: SyncOptions, label: LabelUpdate): const flag = options.flags.find((it) => label.name === it.name); if (doesExist(flag)) { await syncLabelDiff(options, label, { - color: defaultTo(flag.color, label.color), + color: colorizeLabel(flag, options.colors), desc: defaultTo(flag.desc, label.desc), name: flag.name, project: options.project, @@ -152,7 +153,7 @@ export async function syncSingleLabel(options: SyncOptions, label: LabelUpdate): const value = state.values.find((it) => valueName(state, it) === label.name); if (doesExist(value)) { await syncLabelDiff(options, label, { - color: defaultTo(value.color, label.color), + color: colorizeLabel(state, value, options.colors), desc: defaultTo(value.desc, label.desc), name: valueName(state, value), project: options.project, diff --git a/src/utils.ts b/src/utils.ts index ee20c81..f25ac25 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,4 +1,4 @@ -import { doesExist, Optional } from '@apextoaster/js-utils'; +import { doesExist, Optional, mustExist } from '@apextoaster/js-utils'; export function defaultTo(a: Optional, b: T): T { if (doesExist(a)) { @@ -7,3 +7,13 @@ export function defaultTo(a: Optional, b: T): T { return b; } } + +export function defaultUntil(...items: Array>): T { + const result = items.reduce(defaultTo, undefined); + return mustExist(result); +} + +export function randomItem(items: Array, source = Math.random): T { + const idx = Math.floor(source() * items.length); + return items[idx]; +} diff --git a/test/TestUtils.ts b/test/TestUtils.ts index 91d1ec9..01fdc2e 100644 --- a/test/TestUtils.ts +++ b/test/TestUtils.ts @@ -1,6 +1,6 @@ import { expect } from 'chai'; -import { defaultTo } from '../src/utils'; +import { defaultTo, defaultUntil } from '../src/utils'; const TEST_TRUE = 'foo'; const TEST_FALSE = 'bar'; @@ -14,4 +14,15 @@ describe('utils', () => { expect(defaultTo(TEST_TRUE, TEST_FALSE)).to.equal(TEST_TRUE); }); }); + + describe('default until value helper', () => { + it('should return the first defined value', () => { + /* eslint-disable-next-line no-null/no-null */ + expect(defaultUntil(null, null, TEST_TRUE)).to.equal(TEST_TRUE); + /* eslint-disable-next-line no-null/no-null */ + expect(defaultUntil(null, undefined, TEST_TRUE)).to.equal(TEST_TRUE); + expect(defaultUntil(undefined, TEST_TRUE, undefined, undefined, TEST_FALSE)).to.equal(TEST_TRUE); + expect(defaultUntil(undefined, undefined, TEST_TRUE, undefined)).to.equal(TEST_TRUE); + }); + }); });