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;
+}