diff --git a/README.md b/README.md index c20f491ce..ceb8e28e1 100644 --- a/README.md +++ b/README.md @@ -103,27 +103,29 @@ template: | You can configure Release Drafter using the following key in your `.github/release-drafter.yml` file: -| Key | Required | Description | -| ---------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `template` | Required | The template for the body of the draft release. Use [template variables](#template-variables) to insert values. | -| `category-template` | Optional | The template to use for each category. Use [category template variables](#category-template-variables) to insert values. Default: `"## $TITLE"`. | -| `name-template` | Optional | The template for the name of the draft release. For example: `"v$NEXT_PATCH_VERSION"`. | -| `tag-template` | Optional | The template for the tag of the draft release. For example: `"v$NEXT_PATCH_VERSION"`. | -| `version-template` | Optional | The template to use when calculating the next version number for the release. Useful for projects that don't use semantic versioning. Default: `"$MAJOR.$MINOR.$PATCH"` | -| `change-template` | Optional | The template to use for each merged pull request. Use [change template variables](#change-template-variables) to insert values. Default: `"* $TITLE (#$NUMBER) @$AUTHOR"`. | -| `change-title-escapes` | Optional | Characters to escape in `$TITLE` when inserting into `change-template` so that they are not interpreted as Markdown format characters. Default: `""` | -| `no-changes-template` | Optional | The template to use for when there’s no changes. Default: `"* No changes"`. | -| `references` | Optional | The references to listen for configuration updates to `.github/release-drafter.yml`. Refer to [References](#references) to learn more about this | -| `categories` | Optional | Categorize pull requests using labels. Refer to [Categorize Pull Requests](#categorize-pull-requests) to learn more about this option. | -| `exclude-labels` | Optional | Exclude pull requests using labels. Refer to [Exclude Pull Requests](#exclude-pull-requests) to learn more about this option. | -| `include-labels` | Optional | Include only the specified pull requests using labels. Refer to [Include Pull Requests](#include-pull-requests) to learn more about this option. | -| `replacers` | Optional | Search and replace content in the generated changelog body. Refer to [Replacers](#replacers) to learn more about this option. | -| `sort-by` | Optional | Sort changelog by merged_at or title. Can be one of: `merged_at`, `title`. Default: `merged_at`. | -| `sort-direction` | Optional | Sort changelog in ascending or descending order. Can be one of: `ascending`, `descending`. Default: `descending`. | -| `prerelease` | Optional | Mark the draft release as pre-release. Default `false`. | -| `version-resolver` | Optional | Adjust the `$RESOLVED_VERSION` variable using labels. Refer to [Version Resolver](#version-resolver) to learn more about this | -| `filter-by-commitish` | Optional | Filter previous releases to consider only the target branch of the release. Default: `false`. | -| `commitish` | Optional | Specify the target branch of the release. Default: the default branch of the repo. | +| Key | Required | Description | +| -------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `template` | Required | The template for the body of the draft release. Use [template variables](#template-variables) to insert values. | +| `category-template` | Optional | The template to use for each category. Use [category template variables](#category-template-variables) to insert values. Default: `"## $TITLE"`. | +| `name-template` | Optional | The template for the name of the draft release. For example: `"v$NEXT_PATCH_VERSION"`. | +| `tag-template` | Optional | The template for the tag of the draft release. For example: `"v$NEXT_PATCH_VERSION"`. | +| `version-template` | Optional | The template to use when calculating the next version number for the release. Useful for projects that don't use semantic versioning. Default: `"$MAJOR.$MINOR.$PATCH"` | +| `change-template` | Optional | The template to use for each merged pull request. Use [change template variables](#change-template-variables) to insert values. Default: `"* $TITLE (#$NUMBER) @$AUTHOR"`. | +| `change-title-escapes` | Optional | Characters to escape in `$TITLE` when inserting into `change-template` so that they are not interpreted as Markdown format characters. Default: `""` | +| `no-changes-template` | Optional | The template to use for when there’s no changes. Default: `"* No changes"`. | +| `references` | Optional | The references to listen for configuration updates to `.github/release-drafter.yml`. Refer to [References](#references) to learn more about this | +| `categories` | Optional | Categorize pull requests using labels. Refer to [Categorize Pull Requests](#categorize-pull-requests) to learn more about this option. | +| `exclude-labels` | Optional | Exclude pull requests using labels. Refer to [Exclude Pull Requests](#exclude-pull-requests) to learn more about this option. | +| `include-labels` | Optional | Include only the specified pull requests using labels. Refer to [Include Pull Requests](#include-pull-requests) to learn more about this option. | +| `exclude-contributors` | Optional | Exclude specific usernames from the generated `$CONTRIBUTORS` variable. Refer to [Exclude Contributors](#exclude-contributors) to learn more about this option. | +| `no-contributors-template` | Optional | The template to use for `$CONTRIBUTORS` when there's no contributors to list. Default: `"No contributors"`. | +| `replacers` | Optional | Search and replace content in the generated changelog body. Refer to [Replacers](#replacers) to learn more about this option. | +| `sort-by` | Optional | Sort changelog by merged_at or title. Can be one of: `merged_at`, `title`. Default: `merged_at`. | +| `sort-direction` | Optional | Sort changelog in ascending or descending order. Can be one of: `ascending`, `descending`. Default: `descending`. | +| `prerelease` | Optional | Mark the draft release as pre-release. Default `false`. | +| `version-resolver` | Optional | Adjust the `$RESOLVED_VERSION` variable using labels. Refer to [Version Resolver](#version-resolver) to learn more about this | +| `filter-by-commitish` | Optional | Filter previous releases to consider only the target branch of the release. Default: `false`. | +| `commitish` | Optional | Specify the target branch of the release. Default: the default branch of the repo. | Release Drafter also supports [Probot Config](https://github.com/probot/probot-config), if you want to store your configuration files in a central repository. This allows you to share configurations between projects, and create a organization-wide configuration file by creating a repository named `.github` with the file `.github/release-drafter.yml`. @@ -261,6 +263,15 @@ include-labels: Pull requests with the label "app-foo" will be the only pull requests included in the release draft. +## Exclude Contributors + +By default, the `$CONTRIBUTORS` variable will contain the names or usernames of all the contributors of a release. The `exclude-contributors` option allows you to remove certain usernames from that list. This can be useful if don't wish to include yourself, to better highlight only the third-party contributions. + +```yml +exclude-contributors: + - 'myusername' +``` + ## Replacers You can search and replace content in the generated changelog body, using regular expressions, with the `replacers` option. Each replacer is applied in order. @@ -314,7 +325,7 @@ The Release Drafter GitHub Action accepts a number of optional inputs directly i | `tag` | The tag name to be associated with the GitHub release that's created or updated. This will override any `tag-template` specified in your `release-drafter.yml` if defined. | | `version` | The version to be associated with the GitHub release that's created or updated. This will override any version calculated by the release-drafter. | | `publish` | A boolean indicating whether the release being created or updated should be immediately published. This may be useful if the output of a previous workflow step determines that a new version of your project has been (or will be) released, as with [`salsify/action-detect-and-tag-new-version`](https://github.com/salsify/action-detect-and-tag-new-version). | -| `prerelease` | A boolean indicating whether the release being created or updated is a prerelease. | +| `prerelease` | A boolean indicating whether the release being created or updated is a prerelease. | | `commitish` | A string specifying the target branch for the release being created. | ## Action Outputs diff --git a/dist/index.js b/dist/index.js index c5ef74326..b748a8e34 100644 --- a/dist/index.js +++ b/dist/index.js @@ -478,6 +478,8 @@ const DEFAULT_CONFIG = Object.freeze({ categories: [], 'exclude-labels': [], 'include-labels': [], + 'exclude-contributors': [], + 'no-contributors-template': 'No contributors', replacers: [], autolabeler: [], 'sort-by': SORT_BY.mergedAt, @@ -627,19 +629,26 @@ module.exports.findReleases = async ({ ref, context, config }) => { return { draftRelease, lastRelease } } -const contributorsSentence = ({ commits, pullRequests }) => { +const contributorsSentence = ({ commits, pullRequests, config }) => { + const { 'exclude-contributors': excludeContributors } = config + const contributors = new Set() commits.forEach((commit) => { if (commit.author.user) { - contributors.add(`@${commit.author.user.login}`) + if (!excludeContributors.includes(commit.author.user.login)) { + contributors.add(`@${commit.author.user.login}`) + } } else { contributors.add(commit.author.name) } }) pullRequests.forEach((pullRequest) => { - if (pullRequest.author) { + if ( + pullRequest.author && + !excludeContributors.includes(pullRequest.author.login) + ) { contributors.add(`@${pullRequest.author.login}`) } }) @@ -651,8 +660,10 @@ const contributorsSentence = ({ commits, pullRequests }) => { ' and ' + sortedContributors.slice(-1) ) - } else { + } else if (sortedContributors.length === 1) { return sortedContributors[0] + } else { + return config['no-contributors-template'] } } @@ -826,7 +837,7 @@ module.exports.generateReleaseInfo = ({ name = undefined, isPreRelease, shouldDraft, - commitish = undefined + commitish = undefined, }) => { let body = config.template @@ -838,6 +849,7 @@ module.exports.generateReleaseInfo = ({ $CONTRIBUTORS: contributorsSentence({ commits, pullRequests: mergedPullRequests, + config, }), }, config.replacers @@ -978,6 +990,14 @@ const schema = (context) => { .items(Joi.string()) .default(DEFAULT_CONFIG['include-labels']), + 'exclude-contributors': Joi.array() + .items(Joi.string()) + .default(DEFAULT_CONFIG['exclude-contributors']), + + 'no-contributors-template': Joi.string().default( + DEFAULT_CONFIG['no-contributors-template'] + ), + 'sort-by': Joi.string() .valid(SORT_BY.mergedAt, SORT_BY.title) .default(DEFAULT_CONFIG['sort-by']), diff --git a/lib/default-config.js b/lib/default-config.js index d514f31df..a8e98b38d 100644 --- a/lib/default-config.js +++ b/lib/default-config.js @@ -16,6 +16,8 @@ const DEFAULT_CONFIG = Object.freeze({ categories: [], 'exclude-labels': [], 'include-labels': [], + 'exclude-contributors': [], + 'no-contributors-template': 'No contributors', replacers: [], autolabeler: [], 'sort-by': SORT_BY.mergedAt, diff --git a/lib/releases.js b/lib/releases.js index e5115ae4b..891f6aab0 100644 --- a/lib/releases.js +++ b/lib/releases.js @@ -54,19 +54,26 @@ module.exports.findReleases = async ({ ref, context, config }) => { return { draftRelease, lastRelease } } -const contributorsSentence = ({ commits, pullRequests }) => { +const contributorsSentence = ({ commits, pullRequests, config }) => { + const { 'exclude-contributors': excludeContributors } = config + const contributors = new Set() commits.forEach((commit) => { if (commit.author.user) { - contributors.add(`@${commit.author.user.login}`) + if (!excludeContributors.includes(commit.author.user.login)) { + contributors.add(`@${commit.author.user.login}`) + } } else { contributors.add(commit.author.name) } }) pullRequests.forEach((pullRequest) => { - if (pullRequest.author) { + if ( + pullRequest.author && + !excludeContributors.includes(pullRequest.author.login) + ) { contributors.add(`@${pullRequest.author.login}`) } }) @@ -78,8 +85,10 @@ const contributorsSentence = ({ commits, pullRequests }) => { ' and ' + sortedContributors.slice(-1) ) - } else { + } else if (sortedContributors.length === 1) { return sortedContributors[0] + } else { + return config['no-contributors-template'] } } @@ -265,6 +274,7 @@ module.exports.generateReleaseInfo = ({ $CONTRIBUTORS: contributorsSentence({ commits, pullRequests: mergedPullRequests, + config, }), }, config.replacers diff --git a/lib/schema.js b/lib/schema.js index 53dd2c16a..8ce8128a2 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -46,6 +46,14 @@ const schema = (context) => { .items(Joi.string()) .default(DEFAULT_CONFIG['include-labels']), + 'exclude-contributors': Joi.array() + .items(Joi.string()) + .default(DEFAULT_CONFIG['exclude-contributors']), + + 'no-contributors-template': Joi.string().default( + DEFAULT_CONFIG['no-contributors-template'] + ), + 'sort-by': Joi.string() .valid(SORT_BY.mergedAt, SORT_BY.title) .default(DEFAULT_CONFIG['sort-by']), diff --git a/schema.json b/schema.json index a2ea0138c..19327fa07 100644 --- a/schema.json +++ b/schema.json @@ -73,6 +73,17 @@ "type": "string" } }, + "exclude-contributors": { + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "no-contributors-template": { + "default": "No contributors", + "type": "string" + }, "sort-by": { "default": "merged_at", "enum": ["merged_at", "title"], diff --git a/test/fixtures/config/config-with-contributors.yml b/test/fixtures/config/config-with-contributors.yml index 4676aa8a2..e45e8b9a8 100644 --- a/test/fixtures/config/config-with-contributors.yml +++ b/test/fixtures/config/config-with-contributors.yml @@ -1 +1,2 @@ template: 'A big thanks to: $CONTRIBUTORS' +no-contributors-template: 'Nobody' diff --git a/test/fixtures/config/config-with-exclude-contributors.yml b/test/fixtures/config/config-with-exclude-contributors.yml new file mode 100644 index 000000000..09af10b10 --- /dev/null +++ b/test/fixtures/config/config-with-exclude-contributors.yml @@ -0,0 +1,4 @@ +exclude-contributors: + - TimonVS + - Ada Lovelace +template: 'A big thanks to: $CONTRIBUTORS' diff --git a/test/index.test.js b/test/index.test.js index 3771ea26a..5f7dad888 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -648,6 +648,95 @@ describe('release-drafter', () => { expect.assertions(1) }) + + it('uses no-contributors-template when there are no contributors', async () => { + getConfigMock('config-with-contributors.yml') + + nock('https://api.github.com') + .get( + '/repos/toolmantim/release-drafter-test-project/releases?per_page=100' + ) + .reply(200, [require('./fixtures/release')]) + + nock('https://api.github.com') + .post('/graphql', (body) => + body.query.includes('query findCommitsWithAssociatedPullRequests') + ) + .reply(200, require('./fixtures/graphql-commits-empty.json')) + + nock('https://api.github.com') + .post( + '/repos/toolmantim/release-drafter-test-project/releases', + (body) => { + expect(body).toMatchInlineSnapshot(` + Object { + "body": "A big thanks to: Nobody", + "draft": true, + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "", + } + `) + return true + } + ) + .reply(200, require('./fixtures/release')) + + await probot.receive({ + name: 'push', + payload: require('./fixtures/push'), + }) + + expect.assertions(1) + }) + }) + + describe('with exclude-contributors config', () => { + it('excludes matching contributors by username', async () => { + getConfigMock('config-with-exclude-contributors.yml') + + nock('https://api.github.com') + .get( + '/repos/toolmantim/release-drafter-test-project/releases?per_page=100' + ) + .reply(200, [require('./fixtures/release')]) + + nock('https://api.github.com') + .post('/graphql', (body) => + body.query.includes('query findCommitsWithAssociatedPullRequests') + ) + .reply( + 200, + require('./fixtures/__generated__/graphql-commits-merge-commit.json') + ) + + nock('https://api.github.com') + .post( + '/repos/toolmantim/release-drafter-test-project/releases', + (body) => { + expect(body).toMatchInlineSnapshot(` + Object { + "body": "A big thanks to: Ada Lovelace", + "draft": true, + "name": "", + "prerelease": false, + "tag_name": "", + "target_commitish": "", + } + `) + return true + } + ) + .reply(200, require('./fixtures/release')) + + await probot.receive({ + name: 'push', + payload: require('./fixtures/push'), + }) + + expect.assertions(1) + }) }) }) @@ -2200,7 +2289,7 @@ describe('release-drafter', () => { ## Contributors - $CONTRIBUTORS + No contributors ## Previous release @@ -2310,7 +2399,7 @@ describe('release-drafter', () => { ## Contributors - $CONTRIBUTORS + No contributors ## Previous release