diff --git a/docs/api/cautious-journey.changeset.adds.md b/docs/api/cautious-journey.changeset.adds.md new file mode 100644 index 0000000..f863b3f --- /dev/null +++ b/docs/api/cautious-journey.changeset.adds.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [cautious-journey](./cautious-journey.md) > [ChangeSet](./cautious-journey.changeset.md) > [adds](./cautious-journey.changeset.adds.md) + +## ChangeSet.adds property + +Signature: + +```typescript +adds: Array; +``` diff --git a/docs/api/cautious-journey.changeset.md b/docs/api/cautious-journey.changeset.md new file mode 100644 index 0000000..37454de --- /dev/null +++ b/docs/api/cautious-journey.changeset.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [cautious-journey](./cautious-journey.md) > [ChangeSet](./cautious-journey.changeset.md) + +## ChangeSet interface + +A set of labels to add and/or remove. + +Signature: + +```typescript +export interface ChangeSet +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [adds](./cautious-journey.changeset.adds.md) | Array<LabelRef> | | +| [removes](./cautious-journey.changeset.removes.md) | Array<LabelRef> | | +| [requires](./cautious-journey.changeset.requires.md) | Array<LabelRef> | Required labels for this state change to occur. | + diff --git a/docs/api/cautious-journey.changeset.removes.md b/docs/api/cautious-journey.changeset.removes.md new file mode 100644 index 0000000..2768c45 --- /dev/null +++ b/docs/api/cautious-journey.changeset.removes.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [cautious-journey](./cautious-journey.md) > [ChangeSet](./cautious-journey.changeset.md) > [removes](./cautious-journey.changeset.removes.md) + +## ChangeSet.removes property + +Signature: + +```typescript +removes: Array; +``` diff --git a/docs/api/cautious-journey.statechange.matches.md b/docs/api/cautious-journey.changeset.requires.md similarity index 55% rename from docs/api/cautious-journey.statechange.matches.md rename to docs/api/cautious-journey.changeset.requires.md index 0382b4a..a32fa7c 100644 --- a/docs/api/cautious-journey.statechange.matches.md +++ b/docs/api/cautious-journey.changeset.requires.md @@ -1,13 +1,13 @@ -[Home](./index.md) > [cautious-journey](./cautious-journey.md) > [StateChange](./cautious-journey.statechange.md) > [matches](./cautious-journey.statechange.matches.md) +[Home](./index.md) > [cautious-journey](./cautious-journey.md) > [ChangeSet](./cautious-journey.changeset.md) > [requires](./cautious-journey.changeset.requires.md) -## StateChange.matches property +## ChangeSet.requires property Required labels for this state change to occur. Signature: ```typescript -matches: Array; +requires: Array; ``` diff --git a/docs/api/cautious-journey.md b/docs/api/cautious-journey.md index 8cfd212..b7a95ff 100644 --- a/docs/api/cautious-journey.md +++ b/docs/api/cautious-journey.md @@ -23,11 +23,11 @@ | Interface | Description | | --- | --- | +| [ChangeSet](./cautious-journey.changeset.md) | A set of labels to add and/or remove. | | [Remote](./cautious-journey.remote.md) | Basic functions which every remote API must provide. | | [RemoteOptions](./cautious-journey.remoteoptions.md) | | | [ResolveInput](./cautious-journey.resolveinput.md) | Collected inputs for a resolver run. | | [ResolveResult](./cautious-journey.resolveresult.md) | Collected results from a resolver run. | -| [StateChange](./cautious-journey.statechange.md) | The transition between two state values. | | [StateLabel](./cautious-journey.statelabel.md) | Grouped labels: the equivalent of a radio group. | | [StateValue](./cautious-journey.statevalue.md) | One of many values for a particular state. | | [SyncOptions](./cautious-journey.syncoptions.md) | | diff --git a/docs/api/cautious-journey.statechange.md b/docs/api/cautious-journey.statechange.md deleted file mode 100644 index b524755..0000000 --- a/docs/api/cautious-journey.statechange.md +++ /dev/null @@ -1,21 +0,0 @@ - - -[Home](./index.md) > [cautious-journey](./cautious-journey.md) > [StateChange](./cautious-journey.statechange.md) - -## StateChange interface - -The transition between two state values. - -Signature: - -```typescript -export interface StateChange extends ChangeSet -``` -Extends: ChangeSet - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [matches](./cautious-journey.statechange.matches.md) | Array<LabelRef> | Required labels for this state change to occur. | - diff --git a/docs/api/cautious-journey.statevalue.becomes.md b/docs/api/cautious-journey.statevalue.becomes.md index 8d5918c..391d6c4 100644 --- a/docs/api/cautious-journey.statevalue.becomes.md +++ b/docs/api/cautious-journey.statevalue.becomes.md @@ -9,5 +9,5 @@ State changes that could occur to this value. Signature: ```typescript -becomes: Array; +becomes: Array; ``` diff --git a/docs/api/cautious-journey.statevalue.md b/docs/api/cautious-journey.statevalue.md index 3de7e63..7162a9c 100644 --- a/docs/api/cautious-journey.statevalue.md +++ b/docs/api/cautious-journey.statevalue.md @@ -11,11 +11,11 @@ One of many values for a particular state. ```typescript export interface StateValue extends BaseLabel, ChangeSet ``` -Extends: BaseLabel, ChangeSet +Extends: BaseLabel, [ChangeSet](./cautious-journey.changeset.md) ## Properties | Property | Type | Description | | --- | --- | --- | -| [becomes](./cautious-journey.statevalue.becomes.md) | Array<[StateChange](./cautious-journey.statechange.md)> | State changes that could occur to this value. | +| [becomes](./cautious-journey.statevalue.becomes.md) | Array<[ChangeSet](./cautious-journey.changeset.md)> | State changes that could occur to this value. | diff --git a/src/config/index.ts b/src/config/index.ts index d81eaf6..064f2f7 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -1,7 +1,9 @@ +import Ajv from 'ajv'; import { LogLevel, NullLogger } from 'noicejs'; import { FlagLabel, StateLabel } from '../labels'; import { RemoteOptions } from '../remote'; +import * as SCHEMA_DATA from './schema.yml'; /** * Config data for the app, loaded from CLI or DOM. @@ -64,3 +66,21 @@ export function initConfig(): ConfigData { }], }; } + +export const SCHEMA_OPTIONS: Ajv.Options = { + allErrors: true, + coerceTypes: 'array', + missingRefs: 'fail', + removeAdditional: 'failing', + schemaId: 'auto', + useDefaults: true, + verbose: true, +}; + + +export function validateConfig(it: unknown): it is ConfigData { + const ajv = new Ajv(SCHEMA_OPTIONS); + ajv.addSchema(SCHEMA_DATA, 'cautious-journey'); + + return ajv.validate('cautious-journey#/definitions/config', it) === true; +} diff --git a/src/config/schema.yml b/src/config/schema.yml new file mode 100644 index 0000000..b0b35c8 --- /dev/null +++ b/src/config/schema.yml @@ -0,0 +1,102 @@ +$schema: "http://json-schema.org/schema#" +$id: cautious-journey +definitions: + label-ref: + type: object + properties: + name: + type: string + + change-set: + type: object + properties: + adds: + type: array + items: + $ref: "#/definitions/label-ref" + default: [] + removes: + type: array + items: + $ref: "#/definitions/label-ref" + default: [] + + base-label: + type: object + properties: + color: + type: string + desc: + type: string + default: '' + name: + type: string + requires: + type: array + items: + $ref: "#/definitions/label-ref" + default: [] + + flag-label: + allOf: + - $ref: "#/definitions/change-set" + - $ref: "#/definitions/base-label" + + state-label: + allOf: + - $ref: "#/definitions/change-set" + - $ref: "#/definitions/base-label" + - type: object + properties: + values: + type: array + items: + $ref: "#/definitions/state-value" + default: [] + + state-value: + allOf: + - $ref: "#/definitions/change-set" + - $ref: "#/definitions/base-label" + - type: object + properties: + becomes: + type: array + default: [] + + config: + type: object + properties: + logger: + type: object + properties: + level: + type: string + name: + type: string + + projects: + type: array + items: + type: object + properties: + colors: + type: array + items: + type: string + flags: + type: array + items: + $ref: "#/definitions/flag-label" + default: [] + name: + type: string + remote: + type: object + states: + type: array + items: + $ref: "#/definitions/state-label" + default: [] + +type: object \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 654de8d..60d06d0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ import { main } from './main'; -export { FlagLabel, StateChange, StateLabel, StateValue } from './labels'; +export { ChangeSet, FlagLabel, StateLabel, StateValue } from './labels'; export { Remote, RemoteOptions } from './remote'; export { GithubRemote } from './remote/github'; export { GitlabRemote } from './remote/gitlab'; diff --git a/src/labels.ts b/src/labels.ts index 8b9249f..835aa9f 100644 --- a/src/labels.ts +++ b/src/labels.ts @@ -16,6 +16,11 @@ export interface LabelRef { export interface ChangeSet { adds: Array; removes: Array; + + /** + * Required labels for this state change to occur. + */ + requires: Array; } /** @@ -41,8 +46,6 @@ export interface BaseLabel extends ChangeSet { * Label priority. */ priority: number; - - requires: Array; } /** @@ -50,16 +53,6 @@ export interface BaseLabel extends ChangeSet { */ export type FlagLabel = BaseLabel; -/** - * The transition between two state values. - */ -export interface StateChange extends ChangeSet { - /** - * Required labels for this state change to occur. - */ - matches: Array; -} - /** * One of many values for a particular state. */ @@ -67,7 +60,7 @@ export interface StateValue extends BaseLabel, ChangeSet { /** * State changes that could occur to this value. */ - becomes: Array; + becomes: Array; } /** diff --git a/src/main.ts b/src/main.ts index 98c9094..69422f8 100644 --- a/src/main.ts +++ b/src/main.ts @@ -5,7 +5,7 @@ import { DEFAULT_SAFE_SCHEMA, safeLoad } from 'js-yaml'; import { join } from 'path'; import { alea } from 'seedrandom'; -import { ConfigData } from './config'; +import { ConfigData, validateConfig } from './config'; import { Commands, createParser } from './config/args'; import { BunyanLogger } from './logger/bunyan'; import { GithubRemote } from './remote/github'; @@ -36,7 +36,7 @@ async function loadConfig(path: string): Promise { }); const config = safeLoad(rawConfig, { schema }); - if (isNil(config) || typeof config === 'string') { + if (!validateConfig(config)) { throw new Error(); } diff --git a/src/remote/github.ts b/src/remote/github.ts index 2221abb..1fe20ba 100644 --- a/src/remote/github.ts +++ b/src/remote/github.ts @@ -112,7 +112,7 @@ export class GithubRemote implements Remote { const repo = await mustExist(this.request).issues.listForRepo(path); for (const issue of repo.data) { issues.push({ - issue: issue.id.toString(10), + issue: issue.number.toString(), labels: issue.labels.map((l) => l.name), name: issue.title, project: options.project, diff --git a/src/sync.ts b/src/sync.ts index 802271a..1ee348a 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -5,7 +5,7 @@ import { prng } from 'seedrandom'; import { FlagLabel, getLabelColor, getLabelNames, getValueName, StateLabel } from './labels'; import { LabelUpdate, Remote } from './remote'; import { resolveLabels } from './resolve'; -import { defaultTo, defaultUntil } from './utils'; +import { defaultTo, defaultUntil, compareItems } from './utils'; export interface SyncOptions { /** @@ -45,8 +45,11 @@ export async function syncIssueLabels(options: SyncOptions): Promise { states: options.states, }); + options.logger.debug({ changes, errors, issue, labels }, 'resolved labels'); + // TODO: prompt user to update this particular issue - if (changes.length > 0 && errors.length === 0) { + const sameLabels = !compareItems(issue.labels, labels) || changes.length > 0; + if (sameLabels && errors.length === 0) { options.logger.info({ issue, labels }, 'updating issue'); await options.remote.updateIssue({ ...issue, @@ -143,7 +146,7 @@ export async function syncLabelDiff(options: SyncOptions, oldLabel: LabelUpdate, project: options.project, }; - options.logger.debug({ body, newLabel, oldLabel, options }, 'update label'); + options.logger.debug({ body, newLabel, oldLabel }, 'update label'); const resp = await options.remote.updateLabel(body); diff --git a/src/utils.ts b/src/utils.ts index 0a7620d..efca542 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -18,3 +18,20 @@ export function randomItem(items: Array, source: prng): T { const idx = Math.abs(source.int32()) % items.length; return items[idx]; } + +export function compareItems(a: Array, b: Array): boolean { + if (a.length !== b.length) { + return false; + } + + const aSorted = a.sort(); + const bSorted = b.sort(); + + for (let i = 0; i < aSorted.length; ++i) { + if (aSorted[i] !== bSorted[i]) { + return false; + } + } + + return true; +}