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

Add supports for custom replacer #185

Merged
merged 17 commits into from
Apr 26, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ You can configure Release Drafter using the following key in your `.github/relea
| `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. |
| `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. |

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 Expand Up @@ -144,6 +145,18 @@ If your project doesn't follow [Semantic Versioning](https://semver.org) you can

For example, if your project doesn't use patch version numbers, you can set `version-template` to `$MAJOR.$MINOR`. If the current release is version 1.0, then `$NEXT_MINOR_VERSION` will be `1.1`.

## Replacers
halkeye marked this conversation as resolved.
Show resolved Hide resolved

You can search and replace content in the generated changelog body using the `replacers` option. Replacers support searching via regular expressions with the `regex` option, and plain strings with the `search` option. Each replacer is applied in order.

```yml
replacers:
- regex: '/CVE-(\d{4})-(\d+)/g'
replace: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-$1-$2'
- search: 'myname'
replace: 'My Name'
```

## GitHub Installation Permissions

Release Drafter requires full write, because GitHub does not offer a limited scope for only writing releases. **Don't install Release Drafter to your entire GitHub account — only add the repositories you want to draft releases on.**
Expand Down
9 changes: 8 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const getConfig = require('probot-config')
const { isTriggerableBranch } = require('./lib/triggerable-branch')
const { findReleases, generateReleaseInfo } = require('./lib/releases')
const { findCommitsWithAssociatedPullRequests } = require('./lib/commits')
const { validateReplacers } = require('./lib/template')
const log = require('./lib/log')

const configName = 'release-drafter.yml'
Expand All @@ -14,12 +15,18 @@ module.exports = app => {
'no-changes-template': `* No changes`,
'version-template': `$MAJOR.$MINOR.$PATCH`,
categories: [],
'exclude-labels': []
'exclude-labels': [],
replacers: []
}
const config = Object.assign(
defaults,
(await getConfig(context, configName)) || {}
)
config.replacers = validateReplacers({
app,
context,
replacers: config.replacers
})

const branch = context.payload.ref.replace(/^refs\/heads\//, '')

Expand Down
20 changes: 12 additions & 8 deletions lib/releases.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,14 +167,18 @@ module.exports.generateReleaseInfo = ({
}) => {
let body = config.template

body = template(body, {
$PREVIOUS_TAG: lastRelease ? lastRelease.tag_name : '',
$CHANGES: generateChangeLog(mergedPullRequests, config),
$CONTRIBUTORS: contributorsSentence({
commits,
pullRequests: mergedPullRequests
})
})
body = template(
body,
{
$PREVIOUS_TAG: lastRelease ? lastRelease.tag_name : '',
$CHANGES: generateChangeLog(mergedPullRequests, config),
$CONTRIBUTORS: contributorsSentence({
commits,
pullRequests: mergedPullRequests
})
},
config.replacers
halkeye marked this conversation as resolved.
Show resolved Hide resolved
)

if (lastRelease) {
let name = config['name-template'] || ''
Expand Down
43 changes: 41 additions & 2 deletions lib/template.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
const log = require('./log')
const regexParser = require('regex-parser')
const regexEscape = require('escape-string-regexp')

/**
* replaces all uppercase dollar templates with their string representation from obj
* if replacement is undefined in obj the dollar template string is left untouched
*/

const template = (string, obj) => {
return string.replace(/(\$[A-Z_]+)/g, (_, k) => {
const template = (string, obj, customReplacers) => {
let str = string.replace(/(\$[A-Z_]+)/g, (_, k) => {
let result
if (obj[k] === undefined) {
result = k
Expand All @@ -15,6 +19,41 @@ const template = (string, obj) => {
}
return result
})
if (customReplacers) {
customReplacers.forEach(({ search, replace }) => {
str = str.replace(search, replace)
})
}
return str
}

function validateReplacers({ app, context, replacers }) {
return replacers
.map(replacer => {
try {
if (replacer.regex) {
return {
search: regexParser(replacer.regex),
Copy link
Member

@jetersen jetersen Apr 27, 2019

Choose a reason for hiding this comment

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

cheeky beep 😆 I spent an hour thinking my schema addition was borked when in reality you modified the schema. 😡 At myself for not reading the code more closely before deleting it 😆

replace: replacer.replace
}
} else {
// plain string
return {
search: new RegExp(regexEscape(replacer.search), 'g'),
replace: replacer.replace
}
}
} catch (e) {
log({
app,
context,
message: `Bad replacer regex: '${replacer.search}'`
})
return false
}
})
.filter(Boolean)
}

module.exports.template = template
module.exports.validateReplacers = validateReplacers
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@
},
"dependencies": {
"compare-versions": "3.4.0",
"escape-string-regexp": "^2.0.0",
"lodash": "4.17.11",
"probot": "9.2.8",
"probot-config": "1.0.1",
"regex-parser": "^2.2.10",
"request": "2.88.0",
"semver": "6.0.0"
},
Expand Down
8 changes: 8 additions & 0 deletions test/fixtures/config/config-with-replacers.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
replacers:
- regex: /Fixed a bug \(#(\d+)/g
replace: Fixed a bug (#1000

template: |
# What's Changed

$CHANGES
46 changes: 46 additions & 0 deletions test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,52 @@ Previous tag: ''
payload
})

expect.assertions(1)
})
})
describe('custom replacers', () => {
it('replaces a string', async () => {
getConfigMock('config-with-replacers.yml')

nock('https://api.github.com')
.post('/graphql', body =>
body.query.includes('query findCommitsWithAssociatedPullRequests')
)
.reply(200, require('./fixtures/graphql-commits-merge-commit.json'))

nock('https://api.github.com')
.get(
'/repos/toolmantim/release-drafter-test-project/releases?per_page=100'
)
.reply(200, [])

nock('https://api.github.com')
.post(
'/repos/toolmantim/release-drafter-test-project/releases',
body => {
expect(body).toMatchObject({
body: `# What's Changed

* Fixed a bug (#1000) @TimonVS
* Implement homepage (#3) @TimonVS
* Add Prettier config (#2) @TimonVS
* Add EditorConfig (#1) @TimonVS
`,
draft: true,
tag_name: ''
})
return true
}
)
.reply(200)

const payload = require('./fixtures/push')

await probot.receive({
name: 'push',
payload
})

expect.assertions(1)
})
})
Expand Down
78 changes: 77 additions & 1 deletion test/template.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const { template } = require('../lib/template')
const { template, validateReplacers } = require('../lib/template')

describe('template', () => {
it('replaces $A with B', () => {
Expand Down Expand Up @@ -47,4 +47,80 @@ describe('template', () => {

expect(output).toEqual('1.0.0.THIRD LEVEL')
})
it('single custom replacer', () => {
const customReplacer = validateReplacers({
replacers: [
{
regex: '/\\bJENKINS-(\\d+)\\b/g',
replace:
'[https://issues.jenkins-ci.org/browse/JENKINS-$1](JENKINS-$1)'
}
]
})
const output = template(
'This is my body JENKINS-1234 JENKINS-1234 JENKINS-1234',
{},
customReplacer
)

expect(output).toEqual(
'This is my body [https://issues.jenkins-ci.org/browse/JENKINS-1234](JENKINS-1234) [https://issues.jenkins-ci.org/browse/JENKINS-1234](JENKINS-1234) [https://issues.jenkins-ci.org/browse/JENKINS-1234](JENKINS-1234)'
)
})
it('word custom replacer', () => {
const customReplacer = validateReplacers({
replacers: [
{
regex: 'JENKINS',
replace: 'heyyyyyyy'
}
]
})
const output = template('This is my body JENKINS-1234', {}, customReplacer)

expect(output).toEqual('This is my body heyyyyyyy-1234')
})
it('overlapping replacer', () => {
const customReplacer = validateReplacers({
replacers: [
{
search: 'JENKINS',
replace: 'heyyyyyyy'
},
{
search: 'heyyyyyyy',
replace: 'something else'
}
]
})
const output = template('This is my body JENKINS-1234', {}, customReplacer)

expect(output).toEqual('This is my body something else-1234')
})
it('multiple custom replacer', () => {
const customReplacer = validateReplacers({
replacers: [
{
regex: '/\\bJENKINS-(\\d+)\\b/g',
replace:
'[https://issues.jenkins-ci.org/browse/JENKINS-$1](JENKINS-$1)'
},
{
regex:
'/\\[\\[https://issues.jenkins-ci.org/browse/JENKINS-(\\d+)\\]\\(JENKINS-(\\d+)\\)\\]/g',
replace:
'[https://issues.jenkins-ci.org/browse/JENKINS-$1](JENKINS-$1)'
}
]
})
const output = template(
'This is my body [JENKINS-1234] JENKINS-456',
{},
customReplacer
)

expect(output).toEqual(
'This is my body [https://issues.jenkins-ci.org/browse/JENKINS-1234](JENKINS-1234) [https://issues.jenkins-ci.org/browse/JENKINS-456](JENKINS-456)'
)
})
})
10 changes: 10 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1603,6 +1603,11 @@ escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.4, escape-string-regexp@^
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=

escape-string-regexp@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344"
integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==

escodegen@^1.9.1:
version "1.11.1"
resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.11.1.tgz#c485ff8d6b4cdb89e27f4a856e91f118401ca510"
Expand Down Expand Up @@ -4814,6 +4819,11 @@ regex-not@^1.0.0, regex-not@^1.0.2:
extend-shallow "^3.0.2"
safe-regex "^1.1.0"

regex-parser@^2.2.10:
version "2.2.10"
resolved "https://registry.yarnpkg.com/regex-parser/-/regex-parser-2.2.10.tgz#9e66a8f73d89a107616e63b39d4deddfee912b37"
integrity sha512-8t6074A68gHfU8Neftl0Le6KTDwfGAj7IyjPIMSfikI2wJUTHDMaIq42bUsfVnj8mhx0R+45rdUXHGpN164avA==

regexpp@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f"
Expand Down