diff --git a/README.md b/README.md index 5c3789207..a0a3e20c9 100644 --- a/README.md +++ b/README.md @@ -322,24 +322,28 @@ repository: # The app will apply the following settings to it labels: # Labels: define labels for Issues and Pull Requests - - name: bug - color: CC0000 - description: An issue with the system - - - name: feature - # If including a `#`, make sure to wrap it with quotes! - color: '#336699' - description: New functionality. - - - name: first-timers-only - # include the old name to rename an existing label - oldname: Help Wanted - color: '#326699' - - - name: new-label - # include the old name to rename an existing label - oldname: Help Wanted - color: '#326699' + include: + - name: bug + color: CC0000 + description: An issue with the system + + - name: feature + # If including a `#`, make sure to wrap it with quotes! + color: '#336699' + description: New functionality. + + - name: first-timers-only + # include the old name to rename an existing label + oldname: Help Wanted + color: '#326699' + + - name: new-label + # include the old name to rename an existing label + oldname: Help Wanted + color: '#326699' + exclude: + # don't delete any labels created on GitHub that starts with "release" + - name: ^release milestones: # Milestones: define milestones for Issues and Pull Requests diff --git a/lib/plugins/labels.js b/lib/plugins/labels.js index fd981e758..5f6b561f2 100644 --- a/lib/plugins/labels.js +++ b/lib/plugins/labels.js @@ -3,8 +3,21 @@ const NopCommand = require('../nopcommand') const previewHeaders = { accept: 'application/vnd.github.symmetra-preview+json' } module.exports = class Labels extends Diffable { - constructor (...args) { - super(...args) + constructor (nop, github, repo, entries, log) { + const { include, exclude = [] } = entries + if (Array.isArray(include)) { + // Config is object with include array + // labels: + // include: + // - name: label + super(nop, github, repo, include, log) + } else { + // Config is array + // labels: + // - name: label + super(nop, github, repo, entries, log) + } + this.exclude = exclude.filter(item => item?.name).map(item => new RegExp(item.name)) if (this.entries) { this.entries.forEach(label => { @@ -67,6 +80,9 @@ module.exports = class Labels extends Diffable { } remove (existing) { + if (this.isExcluded(existing)) { + return Promise.resolve([]) + } if (this.nop) { return Promise.resolve([ new NopCommand(this.constructor.name, this.repo, this.github.issues.deleteLabel.endpoint(this.wrapAttrs({ name: existing.name })), 'Delete label') @@ -78,4 +94,8 @@ module.exports = class Labels extends Diffable { wrapAttrs (attrs) { return Object.assign({}, attrs, this.repo, { headers: previewHeaders }) } + + isExcluded (label) { + return this.exclude.some(rx => rx.test(label.name)) + } } diff --git a/test/unit/lib/plugins/labels.test.js b/test/unit/lib/plugins/labels.test.js index d0ff6d5fd..a94ffb5bd 100644 --- a/test/unit/lib/plugins/labels.test.js +++ b/test/unit/lib/plugins/labels.test.js @@ -2,14 +2,19 @@ const Labels = require('../../../../lib/plugins/labels') describe('Labels', () => { let github + let log - function configure (config) { - return new Labels(github, { owner: 'bkeepers', repo: 'test' }, config) + function configure(config) { + const nop = false; + return new Labels(nop, github, { owner: 'bkeepers', repo: 'test' }, config, log) } beforeEach(() => { github = { paginate: jest.fn().mockImplementation(() => Promise.resolve()), + repos: { + get: jest.fn().mockImplementation(() => Promise.resolve({})) + }, issues: { listLabelsForRepo: { endpoint: { @@ -21,6 +26,7 @@ describe('Labels', () => { updateLabel: jest.fn().mockImplementation(() => Promise.resolve()) } } + log = { debug: jest.fn(), error: console.error } }) describe('sync', () => { @@ -38,7 +44,7 @@ describe('Labels', () => { { name: 'new-name', oldname: 'update-me', color: 'FFFFFF', description: '' }, { name: 'new-color', color: '999999', description: '' }, { name: 'new-description', color: '#000000', description: 'Hello world' }, - { name: 'added' } + { name: 'added', description: '' } ]) return plugin.sync().then(() => { @@ -53,6 +59,7 @@ describe('Labels', () => { owner: 'bkeepers', repo: 'test', name: 'added', + description: '', headers: { accept: 'application/vnd.github.symmetra-preview+json' } }) @@ -91,5 +98,51 @@ describe('Labels', () => { expect(github.issues.createLabel).toHaveBeenCalledTimes(1) }) }) + + it('does not delete excluded labels', () => { + github.paginate.mockReturnValueOnce(Promise.resolve([ + { name: 'no-change', color: 'FF0000', description: '' }, + { name: 'keep-me', color: '000000', description: '' }, + { name: 'things-to-keep', color: '000000', description: '' }, + { name: 'released on @7.x.x', color: '000000', description: '' }, + { name: 'update-me', color: '0000FF', description: '' }, + { name: 'delete-me', color: '000000', description: '' } + ])) + + const plugin = configure({ + exclude: [ + { name: 'keep' }, + { name: '^released' }, + ], + include: [ + { name: 'no-change', color: 'FF0000', description: '' }, + { name: 'new-name', oldname: 'update-me', color: 'FFFFFF', description: '' }, + { name: 'added', description: '' } + ] + }) + + return plugin.sync().then(() => { + expect(github.issues.deleteLabel).toHaveBeenCalledWith({ + owner: 'bkeepers', + repo: 'test', + name: 'delete-me', + headers: { accept: 'application/vnd.github.symmetra-preview+json' } + }) + + expect(github.issues.updateLabel).toHaveBeenCalledWith({ + owner: 'bkeepers', + repo: 'test', + current_name: 'update-me', + name: 'new-name', + color: 'FFFFFF', + description: '', + headers: { accept: 'application/vnd.github.symmetra-preview+json' } + }) + + expect(github.issues.deleteLabel).toHaveBeenCalledTimes(1) + expect(github.issues.updateLabel).toHaveBeenCalledTimes(1) + expect(github.issues.createLabel).toHaveBeenCalledTimes(1) + }) + }) }) })