Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Autorelease functionality #304

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 14 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,19 +84,20 @@ 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. |
| `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"`. |
| `no-changes-template` | Optional | The template to use for when there’s no changes. Default: `"* No changes"`. |
| `branches` | Optional | The branches to listen for configuration updates to `.github/release-drafter.yml` and for merge commits. Useful if you want to test the app on a pull request branch. Default is the repository’s default branch. |
| `categories` | Optional | Categorize pull requests using labels. Refer to [Categorize Pull Requests](#categorize-pull-requests) to learn more about this option. |
| `exclude-lables` | Optional | Exclude pull requests using labels. Refer to [Exclude Pull Requests](#exclude-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-direction` | Optional | Sort changelog by merged date in ascending or descending order. Can be one of: `ascending`, `descending`. Default: `descending`. |
| Key | Required | Description |
| --------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `template` | Required | The template for the body of the draft release. Use [template variables](#template-variables) to insert values. |
| `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"`. |
| `no-changes-template` | Optional | The template to use for when there’s no changes. Default: `"* No changes"`. |
| `branches` | Optional | The branches to listen for configuration updates to `.github/release-drafter.yml` and for merge commits. Useful if you want to test the app on a pull request branch. Default is the repository’s default branch. |
| `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. |
| `replacers` | Optional | Search and replace content in the generated changelog body. Refer to [Replacers](#replacers) to learn more about this option. |
| `sort-direction` | Optional | Sort changelog by merged date in ascending or descending order. Can be one of: `ascending`, `descending`. Default: `descending`. |
| `auto-release` | Optional | Automatically publish a release on merge to the default branch. By default, the version will be incremented by a patch version, unless the labels 'MINOR' or 'MAJOR' are present on any of the PRs that have gone into the release, in which case the version will be bumped accordingly. |

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`.

Expand Down
25 changes: 23 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const {
SORT_DIRECTIONS
} = require('./lib/sort-pull-requests')
const log = require('./lib/log')
const { incrementVersionBasedOnLabels } = require('./lib/versions')

const configName = 'release-drafter.yml'

Expand All @@ -22,7 +23,8 @@ module.exports = app => {
categories: [],
'exclude-labels': [],
replacers: [],
'sort-direction': SORT_DIRECTIONS.descending
'sort-direction': SORT_DIRECTIONS.descending,
'auto-release': false
}
const config = Object.assign(
defaults,
Expand Down Expand Up @@ -73,16 +75,18 @@ module.exports = app => {
mergedPullRequests: sortedMergedPullRequests
})

let releaseId
if (!draftRelease) {
log({ app, context, message: 'Creating new draft release' })
await context.github.repos.createRelease(
const response = await context.github.repos.createRelease(
context.repo({
name: releaseInfo.name,
tag_name: releaseInfo.tag,
body: releaseInfo.body,
draft: true
})
)
releaseId = response.data.id
} else {
log({ app, context, message: 'Updating existing draft release' })
await context.github.repos.updateRelease(
Expand All @@ -91,6 +95,23 @@ module.exports = app => {
body: releaseInfo.body
})
)
releaseId = draftRelease.id
}

if (config['auto-release']) {
log({ app, context, message: 'Autoreleasing!' })
const thisVersion = incrementVersionBasedOnLabels(
lastRelease,
mergedPullRequests
)
await context.github.repos.updateRelease(
context.repo({
release_id: releaseId,
draft: false,
name: thisVersion, // TODO - how should name and tag_name behave here?
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WDYT?

Copy link
Author

@JimNero009 JimNero009 Sep 17, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jetersen What is the kind of thing you would like to see here? I'm starting to think that this has a fundamental flaw, and that I actually need to go back a few steps and interrupt the templating process for the name and tag variables to make sure they correspond to the version that is just about to be released?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From above you should have:
https://github.com/toolmantim/release-drafter/blob/4e0c93d4a6c5296d865ca2dcd1e4c54d9058c531/index.js#L71-L76

you should have access to releaseInfo.name and releaseInfo.tag

Perhaps your incrementVersionBasedOnLabels should update the releaseInfo.tag?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, of course! Not sure how I didn't see that.
So yeah -- when generating the release info, I can just hook right into that and get it to resolve to different version numbers based on labels. Shouldn't be too difficult... 😅

tag_name: thisVersion
})
)
}
})
}
31 changes: 31 additions & 0 deletions lib/versions.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,34 @@ module.exports.getVersionInfo = (lastRelease, template) => {
})
}
}

module.exports.incrementVersionBasedOnLabels = (lastRelease, pullRequests) => {
const lastVersion = this.getVersionInfo(lastRelease)
const winningLabel = getWinningLabelFrom(pullRequests)
switch (winningLabel) {
case 'MINOR':
JimNero009 marked this conversation as resolved.
Show resolved Hide resolved
return lastVersion.$NEXT_MINOR_VERSION.version
case 'MAJOR':
return lastVersion.$NEXT_MAJOR_VERSION.version
default:
return lastVersion.$NEXT_PATCH_VERSION.version
}
}

const getWinningLabelFrom = pullRequests => {
let winningLabel = 'PATCH'
for (var i = 0; i < pullRequests.length; i++) {
const pr = pullRequests[i]
const labels = pr.labels
for (var j = 0; j < labels.length; j++) {
const label = labels[j]
if (label.name === 'MINOR') {
winningLabel = 'MINOR'
}
if (label.name === 'MAJOR') {
return 'MAJOR'
}
}
}
return winningLabel
}
6 changes: 6 additions & 0 deletions test/fixtures/config/config-with-autorelease.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
template: |
# What's Changed

$CHANGES

auto-release: true
63 changes: 62 additions & 1 deletion test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ Previous tag: ''
expect.assertions(1)
})

it('creates a new draft when run as a GitHub Actiin', async () => {
it('creates a new draft when run as a GitHub Action', async () => {
getConfigMock()

// GitHub actions should use the GITHUB_REF and not the payload ref
Expand Down Expand Up @@ -1159,4 +1159,65 @@ Previous tag: ''
expect.assertions(1)
})
})

describe('with auto-release config', () => {
it('auto-releases if the config value is set', async () => {
getConfigMock('config-with-autorelease.yml')

// All the usual functionaltiy you'd expect:
nock('https://api.github.com')
.get(
'/repos/toolmantim/release-drafter-test-project/releases?per_page=100'
)
.reply(200, [
require('./fixtures/release-2'),
require('./fixtures/release'),
require('./fixtures/release-3')
])

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).toMatchObject({
body: `# What's Changed

* Add documentation (#5) @TimonVS
* Update dependencies (#4) @TimonVS
* Bug fixes (#3) @TimonVS
* Add big feature (#2) @TimonVS
* 👽 Add alien technology (#1) @TimonVS
`,
draft: true,
tag_name: ''
})
return true
}
)
.reply(200, { id: 999 })

// Plus the functionality that releases:
nock('https://api.github.com')
.patch('/repos/toolmantim/release-drafter-test-project/releases/999', {
draft: false,
name: '2.0.1',
tag_name: '2.0.1'
})
.reply(200)

await probot.receive({
name: 'push',
payload: require('./fixtures/push')
})
})
})
})
62 changes: 60 additions & 2 deletions test/versions.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
const { getVersionInfo } = require('../lib/versions')
const {
getVersionInfo,
incrementVersionBasedOnLabels
} = require('../lib/versions')

describe('versions', () => {
describe('version info', () => {
it('extracts a version-like string from the last tag', () => {
const versionInfo = getVersionInfo({
tag_name: 'v10.0.3',
Expand Down Expand Up @@ -54,3 +57,58 @@ describe('versions', () => {
expect(versionInfo).toEqual(undefined)
})
})

describe('increment version based on labels', () => {
it('bumps by a patch number if there are no labels on PRs', () => {
const lastRelease = {
tag_name: '1.0.0',
name: 'Some major release'
}
const prs = [{ labels: [{ name: 'irrelevant' }] }]
const resolvedVersion = incrementVersionBasedOnLabels(lastRelease, prs)

expect(resolvedVersion).toEqual('1.0.1')
})
it('bumps by a patch if there are only PATCH or other irrelevant labels on PRs', () => {
const lastRelease = {
tag_name: '1.0.0',
name: 'Some major release'
}
const prs = [
{ labels: [{ name: 'PATCH' }, { name: 'irrelevant' }] },
{ labels: [{ name: 'foobar' }] }
]
const resolvedVersion = incrementVersionBasedOnLabels(lastRelease, prs)

expect(resolvedVersion).toEqual('1.0.1')
})
it('bumps by a minor version if there are only MINOR, PATCH or irrelevant labels on PRs', () => {
const lastRelease = {
tag_name: '1.0.0',
name: 'Some major release'
}
const prs = [
{ labels: [{ name: 'MINOR' }, { name: 'irrelevant' }] },
{ labels: [{ name: 'PATCH' }] },
{ labels: [{ name: 'foobar' }] }
]
const resolvedVersion = incrementVersionBasedOnLabels(lastRelease, prs)

expect(resolvedVersion).toEqual('1.1.0')
})
it('bumps by a major version if there is a MAJOR label on any of the PRs', () => {
const lastRelease = {
tag_name: '1.0.0',
name: 'Some major release'
}
const prs = [
{ labels: [{ name: 'MINOR' }, { name: 'irrelevant' }] },
{ labels: [{ name: 'PATCH' }] },
{ labels: [{ name: 'MAJOR' }] },
{ labels: [{ name: 'foobar' }] }
]
const resolvedVersion = incrementVersionBasedOnLabels(lastRelease, prs)

expect(resolvedVersion).toEqual('2.0.0')
})
})