Repository: intuit/auto (ts)

Issue:
Ability to have multiple labels map to minor, major patch
**Is your feature request related to a problem? Please describe.**

I want to be able to create a `new-component` label that is equivalent to `minor`

**Describe the solution you'd like**

Be able to configure multiple labels for any of the semver labels, potentially all labels

**Describe alternatives you've considered**

Adding both labels, but it should be better!


Pull request description:
add ability for configured labels to be an array
# What Changed

Labels in config can now supply an array of labels.

# Why

closes #539

Todo:

- [x] Add tests
- [x] Add docs

<!-- GITHUB_RELEASE PR BODY: canary-version -->
Published PR with canary version: `7.4.0-canary.540.7071.0`
<!-- GITHUB_RELEASE PR BODY: canary-version -->


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c0db0ed6..07e728df 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,21 +1,3 @@
-# v7.3.6 (Wed Aug 28 2019)
-
-#### 🐛  Bug Fix
-
-- `@auto-it/core`, `@auto-it/released`
-  - omit commit+prs that: aren't found in repo, are issues, or have the "released" label [#538](https://github.com/intuit/auto/pull/538) ([@hipstersmoothie](https://github.com/hipstersmoothie))
-
-#### 🔩 Dependency Updates
-
-- [Security] Bump mixin-deep from 1.3.1 to 1.3.2 [#536](https://github.com/intuit/auto/pull/536) ([@dependabot-preview[bot]](https://github.com/dependabot-preview[bot]))
-
-#### Authors: 2
-
-- Andrew Lisowski ([@hipstersmoothie](https://github.com/hipstersmoothie))
-- [@dependabot-preview[bot]](https://github.com/dependabot-preview[bot])
-
----
-
 # v7.3.5 (Wed Aug 28 2019)
 
 #### 🐛  Bug Fix
diff --git a/README.md b/README.md
index 559be66c..bace3209 100644
--- a/README.md
+++ b/README.md
@@ -120,11 +120,9 @@ Make sure to read our [code of conduct](./CODE_OF_CONDUCT.md).
 
 ## :rocket: Projects Using `auto` :rocket:
 
-:star: [webpack-inject-plugin](https://github.com/adierkens/webpack-inject-plugin) - A webpack plugin to dynamically inject code into the bundle.
-
-:star: [storybook-addon-notes-github-markdown-css](https://github.com/hipstersmoothie/github-markdown-css) - Make your notes addon look like github markdown.
+:star: [Storybookjs design-system](https://github.com/storybookjs/design-system) - Storybook's official design system
 
-:star: [html-webpack-insert-text-plugin](https://github.com/hipstersmoothie/html-webpack-insert-text-plugin) - Insert text into the head or body of your HTML
+:star: [space-kit](https://github.com/apollographql/space-kit) - Home base for Apollo's design system
 
 :star: [react-glider](https://github.com/hipstersmoothie/react-glider) - A react wrapper for glider.js
 
@@ -132,6 +130,12 @@ Make sure to read our [code of conduct](./CODE_OF_CONDUCT.md).
 
 :star: [reaction](https://github.com/artsy/reaction) - Artsy’s React Components
 
+:star: [emission](https://github.com/artsy/emission) - Artsy’s React Native Components
+
+:star: [webpack-inject-plugin](https://github.com/adierkens/webpack-inject-plugin) - A webpack plugin to dynamically inject code into the bundle.
+
+:star: [html-webpack-insert-text-plugin](https://github.com/hipstersmoothie/html-webpack-insert-text-plugin) - Insert text into the head or body of your HTML
+
 ## :nail_care: `auto` Badge :nail_care:
 
 Does your project use `auto`? Then use our custom badge!
diff --git a/docs/pages/autorc.md b/docs/pages/autorc.md
index 665eeeca..cdcb7d7f 100644
--- a/docs/pages/autorc.md
+++ b/docs/pages/autorc.md
@@ -107,6 +107,49 @@ You can customize everything about a label
 }
 ```
 
+#### Multiple Labels
+
+You have can multiple labels for each of the default labels.
+
+```json
+{
+  "labels": {
+    "major": [
+      {
+        "name": "BREAKING",
+        "title": "SUPER BREAKING CHANGED",
+      },
+      {
+        "name": "Version: Major",
+        "title": "The API has changed:",
+        "description": "Add this label to a PR to create a major release",
+      }
+    ],
+    ...
+  }
+}
+```
+
+::: message is-warning
+:warning: If you override any of the semantic versioning labels the default values are overridden too! Make sure to include that label if you still want it to be used during version calculation.
+:::
+
+```json
+{
+  "labels": {
+    "minor": [
+      "minor",
+      {
+        "name": "New Component",
+        "title": "🧩 New Component",
+        "description": "A new component has been added to the design-system",
+      }
+    ],
+    ...
+  }
+}
+```
+
 #### Changelog Titles
 
 By default auto will create sections in the changelog for the following labels.
diff --git a/lerna.json b/lerna.json
index 8ae47653..84bd9a17 100644
--- a/lerna.json
+++ b/lerna.json
@@ -1,5 +1,5 @@
 {
-  "version": "7.3.6",
+  "version": "7.3.5",
   "npmClient": "yarn",
   "useWorkspaces": true,
   "packages": [
diff --git a/packages/cli/package.json b/packages/cli/package.json
index 954afac5..123fdac9 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -2,7 +2,7 @@
   "name": "auto",
   "bin": "dist/bin/auto.js",
   "description": "CLI tools to help facilitate semantic versioning based on GitHub PR labels",
-  "version": "7.3.6",
+  "version": "7.3.5",
   "author": {
     "name": "Andrew Lisowski",
     "email": "lisowski54@gmail.com"
diff --git a/packages/core/package.json b/packages/core/package.json
index 50fd4df7..ec95e23e 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@auto-it/core",
-  "version": "7.3.6",
+  "version": "7.3.5",
   "description": "Node API for using auto.",
   "main": "dist/auto.js",
   "author": {
diff --git a/packages/core/src/auto.ts b/packages/core/src/auto.ts
index 378d6400..fbd42a85 100644
--- a/packages/core/src/auto.ts
+++ b/packages/core/src/auto.ts
@@ -36,7 +36,7 @@ import Release, {
   ILabelDefinitionMap,
   VersionLabel
 } from './release';
-import SEMVER, { calculateSemVerBump } from './semver';
+import SEMVER, { calculateSemVerBump, IVersionLabels } from './semver';
 import execPromise from './utils/exec-promise';
 import loadPlugin, { IPlugin } from './utils/load-plugins';
 import createLog, { ILogger } from './utils/logger';
@@ -111,7 +111,7 @@ export default class Auto {
   release?: Release;
   git?: Git;
   labels?: ILabelDefinitionMap;
-  semVerLabels?: Map<VersionLabel, string>;
+  semVerLabels?: IVersionLabels;
 
   private versionBump?: SEMVER;
 
@@ -327,7 +327,7 @@ export default class Auto {
       sha = res.data.head.sha;
 
       const labels = await this.git.getLabels(prNumber);
-      const labelTexts = [...this.semVerLabels.values()];
+      const labelValues = [...this.semVerLabels.values()];
       const releaseTag = labels.find(l => l === 'release');
 
       const skipReleaseTag = labels.find(
@@ -336,7 +336,7 @@ export default class Auto {
       );
       const semverTag = labels.find(
         l =>
-          labelTexts.includes(l) &&
+          labelValues.some(labelValue => labelValue.includes(l)) &&
           !!this.release &&
           !this.release.options.skipReleaseLabels.includes(l) &&
           l !== 'release'
diff --git a/packages/core/src/changelog.ts b/packages/core/src/changelog.ts
index 0123f7f7..49447e5b 100644
--- a/packages/core/src/changelog.ts
+++ b/packages/core/src/changelog.ts
@@ -53,12 +53,19 @@ export default class Changelog {
     this.logger = logger;
     this.options = options;
     this.hooks = makeChangelogHooks();
-    this.options.labels.pushToBaseBranch = {
+
+    const pushToMasterLabel = {
       name: 'pushToBaseBranch',
       title: `⚠️  Pushed to ${options.baseBranch}`,
-      description: 'N/A',
-      ...(this.options.labels.pushToBaseBranch || {})
+      description: 'N/A'
     };
+
+    this.options.labels.pushToBaseBranch = this.options.labels.pushToBaseBranch
+      ? this.options.labels.pushToBaseBranch.map(l => ({
+          ...pushToMasterLabel,
+          ...l
+        }))
+      : [pushToMasterLabel];
   }
 
   loadDefaultHooks() {
@@ -140,13 +147,15 @@ export default class Changelog {
   private splitCommits(commits: IExtendedCommit[]): ICommitSplit {
     let currentCommits = [...commits];
     const order = ['major', 'minor', 'patch'];
-    const sections = Object.values(this.options.labels)
-      .filter(label => label.title)
-      .sort((a, b) => {
-        const bIndex = order.indexOf(b.name) + 1 || order.length + 1;
-        const aIndex = order.indexOf(a.name) + 1 || order.length + 1;
+    const sections = Object.entries(this.options.labels)
+      .filter(([, label]) => label.some(l => l.title))
+      .sort(([a], [b]) => {
+        const bIndex = order.indexOf(b) + 1 || order.length + 1;
+        const aIndex = order.indexOf(a) + 1 || order.length + 1;
         return aIndex - bIndex;
-      });
+      })
+      .map(([, labels]) => labels)
+      .reduce((acc, item) => [...acc, ...item], []);
 
     commits
       .filter(
@@ -277,10 +286,12 @@ export default class Changelog {
 
   private async createLabelSection(split: ICommitSplit, sections: string[]) {
     const changelogTitles = Object.entries(this.options.labels).reduce(
-      (titles, [, labelDef]) => {
-        if (labelDef.title) {
-          titles[labelDef.name] = labelDef.title;
-        }
+      (titles, [, labels]) => {
+        labels.map(labelDef => {
+          if (labelDef.title) {
+            titles[labelDef.name] = labelDef.title;
+          }
+        });
 
         return titles;
       },
@@ -308,15 +319,23 @@ export default class Changelog {
         );
 
         return [
-          title,
-          ...[...lines].sort(
-            (a, b) => a.split('\n').length - b.split('\n').length
-          )
-        ].join('\n');
+          title as string,
+          [...lines].sort((a, b) => a.split('\n').length - b.split('\n').length)
+        ] as [string, string[]];
       })
     );
 
-    labelSections.map(section => sections.push(section));
+    const mergedSections = labelSections.reduce(
+      (acc, [title, commits]) => ({
+        ...acc,
+        [title]: [...(acc[title] || []), ...commits]
+      }),
+      {} as Record<string, string[]>
+    );
+
+    Object.entries(mergedSections)
+      .map(([title, lines]) => [title, ...lines].join('\n'))
+      .map(section => sections.push(section));
   }
 
   private async createReleaseNotesSection(
diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts
index 41dcd0df..644acf28 100644
--- a/packages/core/src/config.ts
+++ b/packages/core/src/config.ts
@@ -7,31 +7,51 @@ import { ApiOptions } from './auto-args';
 import {
   defaultLabelDefinition,
   getVersionMap,
-  ILabelDefinition
+  ILabelDefinition,
+  ILabelDefinitionMap
 } from './release';
 import { ILogger } from './utils/logger';
 import tryRequire from './utils/try-require';
 
-function normalizeLabels(config: cosmiconfig.Config) {
+export function normalizeLabel(
+  name: string,
+  label:
+    | string
+    | Partial<ILabelDefinition>
+    | (Partial<ILabelDefinition> | string)[]
+): Partial<ILabelDefinition>[] {
+  const baseLabel = (defaultLabelDefinition[name] || [{}])[0];
+
+  if (typeof label === 'string') {
+    return [{ ...baseLabel, name: label }];
+  }
+
+  if (Array.isArray(label)) {
+    return label
+      .map(l => normalizeLabel(name, l))
+      .reduce((acc, item) => [...acc, ...item], []);
+  }
+
+  if (!label.name) {
+    label.name = name;
+  }
+
+  return [{ ...baseLabel, ...label }];
+}
+
+export function normalizeLabels(config: cosmiconfig.Config) {
   let labels = defaultLabelDefinition;
 
   if (config.labels) {
     const definitions = Object.entries<Partial<ILabelDefinition> | string>(
       config.labels
-    ).map(([label, labelDef]) => {
-      const definition =
-        typeof labelDef === 'string' ? { name: labelDef } : labelDef;
-
-      if (!definition.name) {
-        definition.name = label;
-      }
+    ).map(([label, labelDef]) => ({
+      [label]: normalizeLabel(label, labelDef)
+    }));
 
-      return {
-        [label]: definition
-      };
-    });
-
-    labels = merge(labels, Object.assign({}, ...definitions));
+    labels = merge.all([labels, ...definitions], {
+      arrayMerge: (destinationArray, sourceArray) => sourceArray
+    }) as ILabelDefinitionMap;
   }
 
   return labels;
@@ -81,7 +101,9 @@ export default class Config {
     const skipReleaseLabels = rawConfig.skipReleaseLabels || [];
 
     if (!skipReleaseLabels.includes(semVerLabels.get('skip-release')!)) {
-      skipReleaseLabels.push(semVerLabels.get('skip-release')!);
+      (semVerLabels.get('skip-release') || []).map(l =>
+        skipReleaseLabels.push(l)
+      );
     }
 
     return {
diff --git a/packages/core/src/init.ts b/packages/core/src/init.ts
index ca857f57..1d0bf5ec 100644
--- a/packages/core/src/init.ts
+++ b/packages/core/src/init.ts
@@ -110,15 +110,15 @@ async function getCustomLabels(onlyLabels = false) {
       const { name, title, description } = response.value.values;
       const newLabel: Partial<ILabelDefinition> = {};
 
-      if (name !== labelDef.name) {
+      if (!labelDef.some(l => name !== l.name)) {
         newLabel.name = name;
       }
 
-      if (title !== labelDef.title) {
+      if (!labelDef.some(l => title !== l.title)) {
         newLabel.title = title;
       }
 
-      if (description !== labelDef.description) {
+      if (!labelDef.some(l => description !== l.description)) {
         newLabel.description = description;
       }
 
diff --git a/packages/core/src/release.ts b/packages/core/src/release.ts
index 04cde385..2d71b402 100644
--- a/packages/core/src/release.ts
+++ b/packages/core/src/release.ts
@@ -13,7 +13,7 @@ import { ICreateLabelsOptions } from './auto-args';
 import Changelog from './changelog';
 import Git from './git';
 import LogParse, { IExtendedCommit } from './log-parse';
-import SEMVER, { calculateSemVerBump } from './semver';
+import SEMVER, { calculateSemVerBump, IVersionLabels } from './semver';
 import execPromise from './utils/exec-promise';
 import { dummyLog, ILogger } from './utils/logger';
 import { makeReleaseHooks } from './utils/make-hooks';
@@ -82,59 +82,78 @@ export interface ILabelDefinition {
 }
 
 export interface ILabelDefinitionMap {
-  [label: string]: ILabelDefinition;
+  [label: string]: ILabelDefinition[];
 }
 
 export const defaultLabelDefinition: ILabelDefinitionMap = {
-  [SEMVER.major]: {
-    name: 'major',
-    title: '💥  Breaking Change',
-    description: 'Increment the major version when merged'
-  },
-  [SEMVER.minor]: {
-    name: 'minor',
-    title: '🚀  Enhancement',
-    description: 'Increment the minor version when merged'
-  },
-  [SEMVER.patch]: {
-    name: 'patch',
-    title: '🐛  Bug Fix',
-    description: 'Increment the patch version when merged'
-  },
-  'skip-release': {
-    name: 'skip-release',
-    description: 'Preserve the current version when merged'
-  },
-  release: {
-    name: 'release',
-    description: 'Create a release when this pr is merged'
-  },
-  prerelease: {
-    name: 'prerelease',
-    title: '🚧 Prerelease',
-    description: 'Create a pre-release version when merged'
-  },
-  internal: {
-    name: 'internal',
-    title: '🏠  Internal',
-    description: 'Changes only affect the internal API'
-  },
-  documentation: {
-    name: 'documentation',
-    title: '📝  Documentation',
-    description: 'Changes only affect the documentation'
-  }
+  [SEMVER.major]: [
+    {
+      name: 'major',
+      title: '💥  Breaking Change',
+      description: 'Increment the major version when merged'
+    }
+  ],
+  [SEMVER.minor]: [
+    {
+      name: 'minor',
+      title: '🚀  Enhancement',
+      description: 'Increment the minor version when merged'
+    }
+  ],
+  [SEMVER.patch]: [
+    {
+      name: 'patch',
+      title: '🐛  Bug Fix',
+      description: 'Increment the patch version when merged'
+    }
+  ],
+  'skip-release': [
+    {
+      name: 'skip-release',
+      description: 'Preserve the current version when merged'
+    }
+  ],
+  release: [
+    {
+      name: 'release',
+      description: 'Create a release when this pr is merged'
+    }
+  ],
+  prerelease: [
+    {
+      name: 'prerelease',
+      title: '🚧 Prerelease',
+      description: 'Create a pre-release version when merged'
+    }
+  ],
+  internal: [
+    {
+      name: 'internal',
+      title: '🏠  Internal',
+      description: 'Changes only affect the internal API'
+    }
+  ],
+  documentation: [
+    {
+      name: 'documentation',
+      title: '📝  Documentation',
+      description: 'Changes only affect the documentation'
+    }
+  ]
 };
 
 export const getVersionMap = (labels = defaultLabelDefinition) =>
-  Object.entries(labels).reduce((semVer, [label, labelDef]) => {
-    if (isVersionLabel(label)) {
-      semVer.set(label, labelDef.name);
-    }
+  Object.entries(labels).reduce(
+    (semVer, [label, labelDef]) => {
+      if (isVersionLabel(label)) {
+        semVer.set(label, labelDef.map(l => l.name));
+      }
 
-    return semVer;
-    // tslint:disable-next-line align
-  }, new Map<VersionLabel, string>());
+      return semVer;
+      // tslint:disable-next-line align
+    },
+    new Map() as IVersionLabels
+  );
 
 const readFile = promisify(fs.readFile);
 const writeFile = promisify(fs.writeFile);
@@ -238,7 +257,7 @@ export default class Release {
 
   private readonly git: Git;
   private readonly logger: ILogger;
-  private readonly versionLabels: Map<VersionLabel, string>;
+  private readonly versionLabels: IVersionLabels;
 
   constructor(
     git: Git,
@@ -443,7 +462,7 @@ export default class Release {
     labels: Partial<ILabelDefinitionMap>,
     options: ICreateLabelsOptions = {}
   ) {
-    const oldLabels = await this.git.getProjectLabels();
+    const oldLabels = (await this.git.getProjectLabels()) || [];
     const labelsToCreate = Object.entries(labels).filter(
       ([versionLabel, labelDef]) => {
         if (!labelDef) {
@@ -470,22 +489,29 @@ export default class Release {
 
     if (!options.dryRun) {
       await Promise.all(
-        labelsToCreate.map(async ([label, labelDef]) => {
-          if (!labelDef) {
+        labelsToCreate.map(async ([label, labelDefs]) => {
+          if (!labelDefs) {
             return;
           }
 
-          if (oldLabels && oldLabels.includes(labelDef.name)) {
-            await this.git.updateLabel(label, labelDef);
-          } else {
-            await this.git.createLabel(label, labelDef);
-          }
+          return Promise.all(
+            labelDefs.map(async labelDef => {
+              if (oldLabels.some(o => labelDef.name === o)) {
+                return this.git.updateLabel(label, labelDef);
+              }
+
+              return this.git.createLabel(label, labelDef);
+            })
+          );
         })
       );
     }
 
     const repoMetadata = await this.git.getProject();
-    const justLabelNames = labelsToCreate.map(([name]) => name);
+    const justLabelNames = labelsToCreate.reduce<string[]>(
+      (acc, [, cLabel]) => [...acc, ...(cLabel || []).map(l => l.name)],
+      []
+    );
 
     if (justLabelNames.length > 0) {
       const state = options.dryRun ? 'Would have created' : 'Created';
diff --git a/packages/core/src/semver.ts b/packages/core/src/semver.ts
index aca51e14..993693da 100644
--- a/packages/core/src/semver.ts
+++ b/packages/core/src/semver.ts
@@ -10,7 +10,7 @@ enum SEMVER {
   noVersion = ''
 }
 
-export type IVersionLabels = Map<VersionLabel, string>;
+export type IVersionLabels = Map<VersionLabel, string[]>;
 
 export default SEMVER;
 
@@ -37,14 +37,19 @@ export function calculateSemVerBump(
   { onlyPublishWithReleaseLabel, skipReleaseLabels = [] }: ISemVerOptions = {}
 ) {
   const labelSet = new Set<string>();
+  const skip = labelMap.get('skip-release') || [];
 
-  if (!skipReleaseLabels.includes(labelMap.get('skip-release')!)) {
-    skipReleaseLabels.push(labelMap.get('skip-release')!);
-  }
+  skip.forEach(skipLabel => {
+    if (!skipReleaseLabels.includes(skipLabel)) {
+      skipReleaseLabels.push(skipLabel);
+    }
+  });
 
   labels.map(pr => {
     pr.forEach(label => {
-      const userLabel = [...labelMap.entries()].find(pair => pair[1] === label);
+      const userLabel = [...labelMap.entries()].find(pair =>
+        pair[1].includes(label)
+      );
       labelSet.add(userLabel ? userLabel[0] : label);
     });
   });
@@ -53,10 +58,12 @@ export function calculateSemVerBump(
   let isPrerelease = false;
 
   if (labels.length > 0 && labels[0].length > 0) {
-    isPrerelease = labels[0].includes(labelMap.get('prerelease')!);
+    const prereleaseLabels = labelMap.get('prerelease') || [];
+    const releaseLabels = labelMap.get('release') || [];
+    isPrerelease = labels[0].some(label => prereleaseLabels.includes(label));
     skipRelease = onlyPublishWithReleaseLabel
-      ? !labels[0].includes(labelMap.get('release')!)
-      : !!labels[0].find(label => skipReleaseLabels.includes(label));
+      ? !labels[0].some(label => releaseLabels.includes(label))
+      : labels[0].some(label => skipReleaseLabels.includes(label));
   }
 
   const version = [...labelSet].reduce(getHigherSemverTag, SEMVER.patch);
diff --git a/plugins/chrome/package.json b/plugins/chrome/package.json
index b26e2e97..ed2fa8b5 100644
--- a/plugins/chrome/package.json
+++ b/plugins/chrome/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@auto-it/chrome",
-  "version": "7.3.6",
+  "version": "7.3.5",
   "main": "dist/index.js",
   "description": "Chrome publishing plugin for auto",
   "author": {
diff --git a/plugins/conventional-commits/package.json b/plugins/conventional-commits/package.json
index 1dc5dd11..58f404b5 100644
--- a/plugins/conventional-commits/package.json
+++ b/plugins/conventional-commits/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@auto-it/conventional-commits",
-  "version": "7.3.6",
+  "version": "7.3.5",
   "main": "dist/index.js",
   "description": "Conventional commit plugin for auto",
   "author": {
diff --git a/plugins/conventional-commits/src/index.ts b/plugins/conventional-commits/src/index.ts
index 4013c299..c9dc2149 100644
--- a/plugins/conventional-commits/src/index.ts
+++ b/plugins/conventional-commits/src/index.ts
@@ -22,7 +22,7 @@ export default class ConventionalCommitsPlugin implements IPlugin {
           );
 
           if (conventionalCommit.header && incrementLabel) {
-            commit.labels = [...commit.labels, incrementLabel];
+            commit.labels = [...commit.labels, ...incrementLabel];
           }
         } catch (error) {
           auto.logger.verbose.info(
diff --git a/plugins/git-tag/package.json b/plugins/git-tag/package.json
index 11116eaf..131fa1a5 100644
--- a/plugins/git-tag/package.json
+++ b/plugins/git-tag/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@auto-it/git-tag",
-  "version": "7.3.6",
+  "version": "7.3.5",
   "main": "dist/index.js",
   "description": "Manage your projects version through just a git tag",
   "author": {
diff --git a/plugins/jira/package.json b/plugins/jira/package.json
index 04b16ee8..538c6ef5 100644
--- a/plugins/jira/package.json
+++ b/plugins/jira/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@auto-it/jira",
-  "version": "7.3.6",
+  "version": "7.3.5",
   "main": "dist/index.js",
   "description": "Jira plugin for auto",
   "author": {
diff --git a/plugins/maven/package.json b/plugins/maven/package.json
index c994c787..cfeb89ea 100644
--- a/plugins/maven/package.json
+++ b/plugins/maven/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@auto-it/maven",
-  "version": "7.3.6",
+  "version": "7.3.5",
   "main": "dist/index.js",
   "description": "Maven publishing plugin for auto",
   "author": {
diff --git a/plugins/npm/package.json b/plugins/npm/package.json
index 04864962..e80101b6 100644
--- a/plugins/npm/package.json
+++ b/plugins/npm/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@auto-it/npm",
-  "version": "7.3.6",
+  "version": "7.3.5",
   "main": "dist/index.js",
   "description": "NPM publishing plugin for auto",
   "author": {
diff --git a/plugins/omit-commits/package.json b/plugins/omit-commits/package.json
index eb4faa9f..2fad1b7b 100644
--- a/plugins/omit-commits/package.json
+++ b/plugins/omit-commits/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@auto-it/omit-commits",
-  "version": "7.3.6",
+  "version": "7.3.5",
   "main": "dist/index.js",
   "description": "Omit commits plugin for auto",
   "author": {
diff --git a/plugins/omit-release-notes/package.json b/plugins/omit-release-notes/package.json
index aed636ff..4e740637 100644
--- a/plugins/omit-release-notes/package.json
+++ b/plugins/omit-release-notes/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@auto-it/omit-release-notes",
-  "version": "7.3.6",
+  "version": "7.3.5",
   "main": "dist/index.js",
   "description": "Omit release notes plugin for auto",
   "author": {
diff --git a/plugins/released/package.json b/plugins/released/package.json
index 2de0d8f4..f89c3a0d 100644
--- a/plugins/released/package.json
+++ b/plugins/released/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@auto-it/released",
-  "version": "7.3.6",
+  "version": "7.3.5",
   "main": "dist/index.js",
   "description": "Released plugin for auto. Comments with version + extra",
   "author": {
diff --git a/plugins/slack/package.json b/plugins/slack/package.json
index a87e5713..50e72ee0 100644
--- a/plugins/slack/package.json
+++ b/plugins/slack/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@auto-it/slack",
-  "version": "7.3.6",
+  "version": "7.3.5",
   "main": "dist/index.js",
   "description": "Slack plugin for auto",
   "author": {
diff --git a/plugins/twitter/package.json b/plugins/twitter/package.json
index 09c4898b..b14dc8d6 100644
--- a/plugins/twitter/package.json
+++ b/plugins/twitter/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@auto-it/twitter",
-  "version": "7.3.6",
+  "version": "7.3.5",
   "main": "dist/index.js",
   "description": "Twitter plugin for auto",
   "author": {
diff --git a/plugins/upload-assets/package.json b/plugins/upload-assets/package.json
index b4c6a969..5734b9ff 100644
--- a/plugins/upload-assets/package.json
+++ b/plugins/upload-assets/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@auto-it/upload-assets",
-  "version": "7.3.6",
+  "version": "7.3.5",
   "main": "dist/index.js",
   "description": "Upload assets plugin for auto",
   "author": {
diff --git a/yarn.lock b/yarn.lock
index 0995c84c..05a48234 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -10009,9 +10009,9 @@ mississippi@^3.0.0:
     through2 "^2.0.0"
 
 mixin-deep@^1.2.0:
-  version "1.3.2"
-  resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566"
-  integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe"
+  integrity sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==
   dependencies:
     for-in "^1.0.2"
     is-extendable "^1.0.1"


Test patch:
diff --git a/packages/core/src/__tests__/__snapshots__/auto.test.ts.snap b/packages/core/src/__tests__/__snapshots__/auto.test.ts.snap
index bd9a4a59..32fb76ed 100644
--- a/packages/core/src/__tests__/__snapshots__/auto.test.ts.snap
+++ b/packages/core/src/__tests__/__snapshots__/auto.test.ts.snap
@@ -5,44 +5,60 @@ exports[`Auto createLabels should create the labels 1`] = `
   "calls": Array [
     Array [
       Object {
-        "documentation": Object {
-          "description": "Changes only affect the documentation",
-          "name": "documentation",
-          "title": "📝  Documentation",
-        },
-        "internal": Object {
-          "description": "Changes only affect the internal API",
-          "name": "internal",
-          "title": "🏠  Internal",
-        },
-        "major": Object {
-          "description": "Increment the major version when merged",
-          "name": "Version: Major",
-          "title": "💥  Breaking Change",
-        },
-        "minor": Object {
-          "description": "Increment the minor version when merged",
-          "name": "Version: Minor",
-          "title": "🚀  Enhancement",
-        },
-        "patch": Object {
-          "description": "Increment the patch version when merged",
-          "name": "Version: Patch",
-          "title": "🐛  Bug Fix",
-        },
-        "prerelease": Object {
-          "description": "Create a pre-release version when merged",
-          "name": "prerelease",
-          "title": "🚧 Prerelease",
-        },
-        "release": Object {
-          "description": "Create a release when this pr is merged",
-          "name": "release",
-        },
-        "skip-release": Object {
-          "description": "Preserve the current version when merged",
-          "name": "skip-release",
-        },
+        "documentation": Array [
+          Object {
+            "description": "Changes only affect the documentation",
+            "name": "documentation",
+            "title": "📝  Documentation",
+          },
+        ],
+        "internal": Array [
+          Object {
+            "description": "Changes only affect the internal API",
+            "name": "internal",
+            "title": "🏠  Internal",
+          },
+        ],
+        "major": Array [
+          Object {
+            "description": "Increment the major version when merged",
+            "name": "Version: Major",
+            "title": "💥  Breaking Change",
+          },
+        ],
+        "minor": Array [
+          Object {
+            "description": "Increment the minor version when merged",
+            "name": "Version: Minor",
+            "title": "🚀  Enhancement",
+          },
+        ],
+        "patch": Array [
+          Object {
+            "description": "Increment the patch version when merged",
+            "name": "Version: Patch",
+            "title": "🐛  Bug Fix",
+          },
+        ],
+        "prerelease": Array [
+          Object {
+            "description": "Create a pre-release version when merged",
+            "name": "prerelease",
+            "title": "🚧 Prerelease",
+          },
+        ],
+        "release": Array [
+          Object {
+            "description": "Create a release when this pr is merged",
+            "name": "release",
+          },
+        ],
+        "skip-release": Array [
+          Object {
+            "description": "Preserve the current version when merged",
+            "name": "skip-release",
+          },
+        ],
       },
       Object {},
     ],
@@ -61,44 +77,60 @@ Object {
   "baseBranch": "master",
   "extends": "@artsy",
   "labels": Object {
-    "documentation": Object {
-      "description": "Changes only affect the documentation",
-      "name": "documentation",
-      "title": "📝  Documentation",
-    },
-    "internal": Object {
-      "description": "Changes only affect the internal API",
-      "name": "internal",
-      "title": "🏠  Internal",
-    },
-    "major": Object {
-      "description": "Increment the major version when merged",
-      "name": "major",
-      "title": "💥  Breaking Change",
-    },
-    "minor": Object {
-      "description": "Increment the minor version when merged",
-      "name": "minor",
-      "title": "🚀  Enhancement",
-    },
-    "patch": Object {
-      "description": "Increment the patch version when merged",
-      "name": "patch",
-      "title": "🐛  Bug Fix",
-    },
-    "prerelease": Object {
-      "description": "Create a pre-release version when merged",
-      "name": "prerelease",
-      "title": "🚧 Prerelease",
-    },
-    "release": Object {
-      "description": "Create a release when this pr is merged",
-      "name": "release",
-    },
-    "skip-release": Object {
-      "description": "Preserve the current version when merged",
-      "name": "skip-release",
-    },
+    "documentation": Array [
+      Object {
+        "description": "Changes only affect the documentation",
+        "name": "documentation",
+        "title": "📝  Documentation",
+      },
+    ],
+    "internal": Array [
+      Object {
+        "description": "Changes only affect the internal API",
+        "name": "internal",
+        "title": "🏠  Internal",
+      },
+    ],
+    "major": Array [
+      Object {
+        "description": "Increment the major version when merged",
+        "name": "major",
+        "title": "💥  Breaking Change",
+      },
+    ],
+    "minor": Array [
+      Object {
+        "description": "Increment the minor version when merged",
+        "name": "minor",
+        "title": "🚀  Enhancement",
+      },
+    ],
+    "patch": Array [
+      Object {
+        "description": "Increment the patch version when merged",
+        "name": "patch",
+        "title": "🐛  Bug Fix",
+      },
+    ],
+    "prerelease": Array [
+      Object {
+        "description": "Create a pre-release version when merged",
+        "name": "prerelease",
+        "title": "🚧 Prerelease",
+      },
+    ],
+    "release": Array [
+      Object {
+        "description": "Create a release when this pr is merged",
+        "name": "release",
+      },
+    ],
+    "skip-release": Array [
+      Object {
+        "description": "Preserve the current version when merged",
+        "name": "skip-release",
+      },
+    ],
   },
   "onlyPublishWithReleaseLabel": true,
   "owner": "foo",
@@ -116,44 +148,60 @@ Object {
   "baseBranch": "master",
   "extends": "./fake.json",
   "labels": Object {
-    "documentation": Object {
-      "description": "Changes only affect the documentation",
-      "name": "documentation",
-      "title": "📝  Documentation",
-    },
-    "internal": Object {
-      "description": "Changes only affect the internal API",
-      "name": "internal",
-      "title": "🏠  Internal",
-    },
-    "major": Object {
-      "description": "Increment the major version when merged",
-      "name": "major",
-      "title": "💥  Breaking Change",
-    },
-    "minor": Object {
-      "description": "Increment the minor version when merged",
-      "name": "minor",
-      "title": "🚀  Enhancement",
-    },
-    "patch": Object {
-      "description": "Increment the patch version when merged",
-      "name": "patch",
-      "title": "🐛  Bug Fix",
-    },
-    "prerelease": Object {
-      "description": "Create a pre-release version when merged",
-      "name": "prerelease",
-      "title": "🚧 Prerelease",
-    },
-    "release": Object {
-      "description": "Create a release when this pr is merged",
-      "name": "release",
-    },
-    "skip-release": Object {
-      "description": "Preserve the current version when merged",
-      "name": "skip-release",
-    },
+    "documentation": Array [
+      Object {
+        "description": "Changes only affect the documentation",
+        "name": "documentation",
+        "title": "📝  Documentation",
+      },
+    ],
+    "internal": Array [
+      Object {
+        "description": "Changes only affect the internal API",
+        "name": "internal",
+        "title": "🏠  Internal",
+      },
+    ],
+    "major": Array [
+      Object {
+        "description": "Increment the major version when merged",
+        "name": "major",
+        "title": "💥  Breaking Change",
+      },
+    ],
+    "minor": Array [
+      Object {
+        "description": "Increment the minor version when merged",
+        "name": "minor",
+        "title": "🚀  Enhancement",
+      },
+    ],
+    "patch": Array [
+      Object {
+        "description": "Increment the patch version when merged",
+        "name": "patch",
+        "title": "🐛  Bug Fix",
+      },
+    ],
+    "prerelease": Array [
+      Object {
+        "description": "Create a pre-release version when merged",
+        "name": "prerelease",
+        "title": "🚧 Prerelease",
+      },
+    ],
+    "release": Array [
+      Object {
+        "description": "Create a release when this pr is merged",
+        "name": "release",
+      },
+    ],
+    "skip-release": Array [
+      Object {
+        "description": "Preserve the current version when merged",
+        "name": "skip-release",
+      },
+    ],
   },
   "owner": "foo",
   "repo": "bar",
diff --git a/packages/core/src/__tests__/__snapshots__/changelog.test.ts.snap b/packages/core/src/__tests__/__snapshots__/changelog.test.ts.snap
index 046c0dbd..72c6afa7 100644
--- a/packages/core/src/__tests__/__snapshots__/changelog.test.ts.snap
+++ b/packages/core/src/__tests__/__snapshots__/changelog.test.ts.snap
@@ -134,7 +134,7 @@ exports[`generateReleaseNotes should create note for PR commits 1`] = `
 - Adam Dierkens (adam@dierkens.com)"
 `;
 
-exports[`generateReleaseNotes should create note for PR commits without labels 1`] = `
+exports[`generateReleaseNotes should create note for PR commits with only non config labels 1`] = `
 "#### 🐛  Bug Fix
 
 - Some Feature [#1234](https://github.custom.com/foobar/auto/pull/1234) (adam@dierkens.com)
@@ -144,7 +144,7 @@ exports[`generateReleaseNotes should create note for PR commits without labels 1
 - Adam Dierkens (adam@dierkens.com)"
 `;
 
-exports[`generateReleaseNotes should create note for PR commits with only non config labels 1`] = `
+exports[`generateReleaseNotes should create note for PR commits without labels 1`] = `
 "#### 🐛  Bug Fix
 
 - Some Feature [#1234](https://github.custom.com/foobar/auto/pull/1234) (adam@dierkens.com)
@@ -179,6 +179,17 @@ exports[`generateReleaseNotes should include prs with released label 1`] = `
 - Adam Dierkens (adam@dierkens.com)"
 `;
 
+exports[`generateReleaseNotes should merge sections with same changelog title 1`] = `
+"#### Enhancement
+
+- Second Feature [#1236](https://github.custom.com/foobar/auto/pull/1236) (adam@dierkens.com)
+- First Feature [#1235](https://github.custom.com/foobar/auto/pull/1235) (adam@dierkens.com)
+
+#### Authors: 1
+
+- Adam Dierkens (adam@dierkens.com)"
+`;
+
 exports[`generateReleaseNotes should omit authors with invalid email addresses 1`] = `
 "#### 🚀  Enhancement
 
diff --git a/packages/core/src/__tests__/auto.test.ts b/packages/core/src/__tests__/auto.test.ts
index 8b5e87d4..e82a52a9 100644
--- a/packages/core/src/__tests__/auto.test.ts
+++ b/packages/core/src/__tests__/auto.test.ts
@@ -116,12 +116,12 @@ describe('Auto', () => {
     await auto.loadConfig();
 
     expect([...auto.semVerLabels!.values()]).toEqual([
-      'Version: Major',
-      'Version: Minor',
-      'Version: Patch',
-      'skip-release',
-      'release',
-      'prerelease'
+      ['Version: Major'],
+      ['Version: Minor'],
+      ['Version: Patch'],
+      ['skip-release'],
+      ['release'],
+      ['prerelease']
     ]);
   });
 
@@ -154,11 +154,13 @@ describe('Auto', () => {
     auto.logger = dummyLog();
     await auto.loadConfig();
 
-    expect(auto.release!.options.labels.minor).toEqual({
-      description: 'Increment the minor version when merged',
-      name: 'feature',
-      title: '🚀  Enhancement'
-    });
+    expect(auto.release!.options.labels.minor).toEqual([
+      {
+        description: 'Increment the minor version when merged',
+        name: 'feature',
+        title: '🚀  Enhancement'
+      }
+    ]);
   });
 
   test('should be able to omit properties from label definition', async () => {
@@ -176,11 +178,13 @@ describe('Auto', () => {
     auto.logger = dummyLog();
     await auto.loadConfig();
 
-    expect(auto.release!.options.labels.minor).toEqual({
-      description: 'This is a test',
-      name: 'minor',
-      title: '🚀  Enhancement'
-    });
+    expect(auto.release!.options.labels.minor).toEqual([
+      {
+        description: 'This is a test',
+        name: 'minor',
+        title: '🚀  Enhancement'
+      }
+    ]);
   });
 
   test('arbitrary labels should be able to omit name', async () => {
@@ -198,10 +202,12 @@ describe('Auto', () => {
     auto.logger = dummyLog();
     await auto.loadConfig();
 
-    expect(auto.release!.options.labels.fooBar).toEqual({
-      description: 'This is a test',
-      name: 'fooBar'
-    });
+    expect(auto.release!.options.labels.fooBar).toEqual([
+      {
+        description: 'This is a test',
+        name: 'fooBar'
+      }
+    ]);
   });
 
   describe('createLabels', () => {
@@ -921,20 +927,24 @@ describe('hooks', () => {
     auto.logger = dummyLog();
 
     auto.hooks.modifyConfig.tap('test', testConfig => {
-      testConfig.labels.released = {
-        name: 'released',
-        description: 'This issue/pull request has been released'
-      };
+      testConfig.labels.released = [
+        {
+          name: 'released',
+          description: 'This issue/pull request has been released'
+        }
+      ];
 
       return testConfig;
     });
 
     await auto.loadConfig();
 
-    expect(auto.labels!.released).toEqual({
-      description: 'This issue/pull request has been released',
-      name: 'released'
-    });
+    expect(auto.labels!.released).toEqual([
+      {
+        description: 'This issue/pull request has been released',
+        name: 'released'
+      }
+    ]);
   });
 
   describe('logParse', () => {
@@ -944,7 +954,7 @@ describe('hooks', () => {
 
       auto.hooks.onCreateLogParse.tap('test', logParse => {
         logParse.hooks.parseCommit.tap('test parse', commit => {
-          commit.labels = [auto.semVerLabels!.get(SEMVER.major)!];
+          commit.labels = [...auto.semVerLabels!.get(SEMVER.major)!];
           return commit;
         });
       });
@@ -964,7 +974,7 @@ describe('hooks', () => {
 
       auto.hooks.onCreateLogParse.tap('test', logParse => {
         logParse.hooks.parseCommit.tap('test parse', commit => {
-          commit.labels = [auto.semVerLabels!.get(SEMVER.major)!];
+          commit.labels = [...auto.semVerLabels!.get(SEMVER.major)!];
           return commit;
         });
       });
diff --git a/packages/core/src/__tests__/changelog.test.ts b/packages/core/src/__tests__/changelog.test.ts
index f30d74e1..2f1e988b 100644
--- a/packages/core/src/__tests__/changelog.test.ts
+++ b/packages/core/src/__tests__/changelog.test.ts
@@ -312,11 +312,13 @@ describe('generateReleaseNotes', () => {
     const options = testOptions();
     options.labels = {
       ...options.labels,
-      pushToBaseBranch: {
-        name: 'pushToBaseBranch',
-        title: 'Custom Title',
-        description: 'N/A'
-      }
+      pushToBaseBranch: [
+        {
+          name: 'pushToBaseBranch',
+          title: 'Custom Title',
+          description: 'N/A'
+        }
+      ]
     };
 
     const changelog = new Changelog(dummyLog(), options);
@@ -346,17 +348,56 @@ describe('generateReleaseNotes', () => {
     const options = testOptions();
     options.labels = {
       ...options.labels,
-      minor: {
-        name: 'Version: Minor',
-        title: 'Woo Woo New Features',
-        description: 'N/A'
+      minor: [
+        {
+          name: 'Version: Minor',
+          title: 'Woo Woo New Features',
+          description: 'N/A'
+        }
+      ]
+    };
+
+    const changelog = new Changelog(dummyLog(), options);
+    changelog.loadDefaultHooks();
+
+    const commits = await logParse.normalizeCommits([
+      {
+        hash: '2',
+        authorName: 'Adam Dierkens',
+        authorEmail: 'adam@dierkens.com',
+        subject: 'First Feature (#1235)',
+        labels: ['Version: Minor']
       }
+    ]);
+
+    expect(await changelog.generateReleaseNotes(commits)).toMatchSnapshot();
+  });
+
+  test('should merge sections with same changelog title', async () => {
+    const options = testOptions();
+    options.labels = {
+      ...options.labels,
+      minor: [
+        { name: 'new-component', title: 'Enhancement' },
+        {
+          name: 'Version: Minor',
+          title: 'Enhancement',
+          description: 'N/A'
+        }
+      ]
     };
 
     const changelog = new Changelog(dummyLog(), options);
     changelog.loadDefaultHooks();
 
     const commits = await logParse.normalizeCommits([
+      {
+        hash: '3',
+        authorName: 'Adam Dierkens',
+        authorEmail: 'adam@dierkens.com',
+        subject: 'Second Feature (#1236)',
+        labels: ['new-component']
+      },
       {
         hash: '2',
         authorName: 'Adam Dierkens',
diff --git a/packages/core/src/__tests__/config.test.ts b/packages/core/src/__tests__/config.test.ts
index 0a35420c..ec40aa68 100644
--- a/packages/core/src/__tests__/config.test.ts
+++ b/packages/core/src/__tests__/config.test.ts
@@ -1,4 +1,4 @@
-import Config from '../config';
+import Config, { normalizeLabel, normalizeLabels } from '../config';
 import { dummyLog } from '../utils/logger';
 
 const fetchSpy = jest.fn();
@@ -15,6 +15,56 @@ const log = dummyLog();
 const importMock = jest.fn();
 jest.mock('import-cwd', () => (path: string) => importMock(path));
 
+describe('normalizeLabel', () => {
+  test('should handle string', () => {
+    expect(normalizeLabel('foo', 'foo')).toEqual([{ name: 'foo' }]);
+  });
+
+  test('should handle object', () => {
+    const label = { name: 'foo', description: 'something' };
+    expect(normalizeLabel('foo', label)).toEqual([label]);
+  });
+
+  test('should attach name', () => {
+    const label = { description: 'something' };
+    expect(normalizeLabel('foo', label)).toEqual([
+      { name: 'foo', description: 'something' }
+    ]);
+  });
+
+  test('should handle arrays', () => {
+    const label = { name: 'foo', description: 'something' };
+    expect(normalizeLabel('major', ['major', label])).toEqual([
+      {
+        description: 'Increment the major version when merged',
+        name: 'major',
+        title: '💥  Breaking Change'
+      },
+      { ...label, title: '💥  Breaking Change' }
+    ]);
+  });
+});
+
+describe('normalizeLabels', () => {
+  test('user labels should override defaults', () => {
+    expect(normalizeLabels({}).minor).toEqual([
+      {
+        description: 'Increment the minor version when merged',
+        name: 'minor',
+        title: '🚀  Enhancement'
+      }
+    ]);
+
+    expect(normalizeLabels({ labels: { minor: ['foo'] } }).minor).toEqual([
+      {
+        description: 'Increment the minor version when merged',
+        name: 'foo',
+        title: '🚀  Enhancement'
+      }
+    ]);
+  });
+});
+
 describe('loadExtendConfig', () => {
   test('should reject when no config found', async () => {
     const config = new Config(log);
diff --git a/packages/core/src/__tests__/release.test.ts b/packages/core/src/__tests__/release.test.ts
index d34df1fe..16fc437f 100644
--- a/packages/core/src/__tests__/release.test.ts
+++ b/packages/core/src/__tests__/release.test.ts
@@ -2,7 +2,11 @@ import merge from 'deepmerge';
 import { parse } from 'graphql';
 import Git from '../git';
 import LogParse from '../log-parse';
-import Release, { buildSearchQuery, defaultLabelDefinition } from '../release';
+import Release, {
+  buildSearchQuery,
+  defaultLabelDefinition,
+  getVersionMap
+} from '../release';
 import SEMVER from '../semver';
 import { dummyLog } from '../utils/logger';
 import makeCommitFromMsg from './make-commit-from-msg';
@@ -104,6 +108,27 @@ const git = new Git({
   token: 'MY_TOKEN'
 });
 
+describe('getVersionMap', () => {
+  test('should return the default map', () => {
+    expect(getVersionMap()).toEqual(
+      new Map([
+        ['major', ['major']],
+        ['minor', ['minor']],
+        ['patch', ['patch']],
+        ['skip-release', ['skip-release']],
+        ['release', ['release']],
+        ['prerelease', ['prerelease']]
+      ])
+    );
+  });
+
+  test('should add custom labels', () => {
+    expect(
+      getVersionMap({ major: [{ name: 'major' }, { name: 'BREAKING' }] })
+    ).toEqual(new Map([['major', ['major', 'BREAKING']]]));
+  });
+});
+
 describe('Release', () => {
   beforeEach(() => {
     getGitLog.mockClear();
@@ -838,10 +863,10 @@ describe('Release', () => {
 
     test('should be able to configure labels', async () => {
       const customLabels = merge(defaultLabelDefinition, {
-        [SEMVER.major]: { name: 'Version: Major' },
-        [SEMVER.minor]: { name: 'Version: Minor' },
-        [SEMVER.patch]: { name: 'Version: Patch' },
-        release: { name: 'Deploy' }
+        [SEMVER.major]: [{ name: 'Version: Major' }],
+        [SEMVER.minor]: [{ name: 'Version: Minor' }],
+        [SEMVER.patch]: [{ name: 'Version: Patch' }],
+        release: [{ name: 'Deploy' }]
       });
 
       const gh = new Release(git, {
@@ -877,9 +902,9 @@ describe('Release', () => {
     test('should add labels', async () => {
       const gh = new Release(git);
       const customLabels = {
-        [SEMVER.major]: { name: '1', description: 'major' },
-        [SEMVER.minor]: { name: '2', description: 'minor' },
-        [SEMVER.patch]: { name: '3', description: 'patch' }
+        [SEMVER.major]: [{ name: '1', description: 'major' }],
+        [SEMVER.minor]: [{ name: '2', description: 'minor' }],
+        [SEMVER.patch]: [{ name: '3', description: 'patch' }]
       };
 
       await gh.addLabelsToProject(customLabels);
@@ -913,12 +938,12 @@ describe('Release', () => {
       );
 
       const labels = {
-        [SEMVER.patch]: { name: '3', description: 'three' }
+        [SEMVER.patch]: [{ name: '3', description: 'three' }]
       };
 
       await gh.addLabelsToProject(labels);
 
-      expect(mockLogger.log.log).toHaveBeenCalledWith('Created labels: patch');
+      expect(mockLogger.log.log).toHaveBeenCalledWith('Created labels: 3');
       expect(mockLogger.log.log).toHaveBeenCalledWith(
         '\nYou can see these, and more at https://github.com/web/site/labels'
       );
@@ -927,8 +952,8 @@ describe('Release', () => {
     test('should not add old labels', async () => {
       const gh = new Release(git);
       const labels = {
-        [SEMVER.major]: { name: '1', description: 'major' },
-        [SEMVER.minor]: { name: '2', description: 'minor' }
+        [SEMVER.major]: [{ name: '1', description: 'major' }],
+        [SEMVER.minor]: [{ name: '2', description: 'minor' }]
       };
 
       getProjectLabels.mockReturnValueOnce(['1']);
@@ -951,7 +976,7 @@ describe('Release', () => {
         baseBranch: 'master'
       });
       const labels = {
-        release: { name: 'deploy', description: 'release the code' }
+        release: [{ name: 'deploy', description: 'release the code' }]
       };
 
       await gh.addLabelsToProject(labels);
@@ -981,7 +1006,9 @@ describe('Release', () => {
         baseBranch: 'master'
       });
       const labels = {
-        'skip-release': { name: 'no!', description: 'Do not create a release' }
+        'skip-release': [
+          { name: 'no!', description: 'Do not create a release' }
+        ]
       };
 
       await gh.addLabelsToProject(labels);