diff --git a/README.md b/README.md index d21e83809..36ddef5ed 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,7 @@ You can configure Release Drafter using the following key in your `.github/relea | `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"`. | +| `tag-prefix` | Optional | A known prefix used to filter release tags. For matching tags, this prefix is stripped before attempting to parse the version. Default: `""` | | `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: `""` | @@ -131,6 +132,7 @@ You can configure Release Drafter using the following key in your `.github/relea | `version-resolver` | Optional | Adjust the `$RESOLVED_VERSION` variable using labels. Refer to [Version Resolver](#version-resolver) to learn more about this | | `commitish` | Optional | The release target, i.e. branch or commit it should point to. Default: the ref that release-drafter runs for, e.g. `refs/heads/master` if configured to run on pushes to `master`. | | `filter-by-commitish` | Optional | Filter previous releases to consider only those with the target matching `commitish`. Default: `false`. | +| `include-paths` | Optional | Restrict pull requests included in the release notes to only the pull requests that modified any of the paths in this array. Supports files and directories. Default: `[]` | 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`. diff --git a/dist/index.js b/dist/index.js index 6e5a773ba..991f017d6 100644 --- a/dist/index.js +++ b/dist/index.js @@ -128605,12 +128605,16 @@ module.exports = (app, { getRouter }) => { } const targetCommitish = commitish || config['commitish'] || ref - const filterByCommitish = config['filter-by-commitish'] + const { + 'filter-by-commitish': filterByCommitish, + 'tag-prefix': tagPrefix, + } = config const { draftRelease, lastRelease } = await findReleases({ context, targetCommitish, filterByCommitish, + tagPrefix, }) const { commits, pullRequests: mergedPullRequests } = @@ -128997,6 +129001,7 @@ const { SORT_BY, SORT_DIRECTIONS } = __nccwpck_require__(11940) const DEFAULT_CONFIG = Object.freeze({ 'name-template': '', 'tag-template': '', + 'tag-prefix': '', 'change-template': `* $TITLE (#$NUMBER) @$AUTHOR`, 'change-title-escapes': '', 'no-changes-template': `* No changes`, @@ -129134,6 +129139,7 @@ const findReleases = async ({ context, targetCommitish, filterByCommitish, + tagPrefix, }) => { let releaseCount = 0 let releases = await context.octokit.paginate( @@ -129156,12 +129162,15 @@ const findReleases = async ({ // `refs/heads/branch` and `branch` are the same thing in this context const headRefRegex = /^refs\/heads\// const targetCommitishName = targetCommitish.replace(headRefRegex, '') - const filteredReleases = filterByCommitish + const commitishFilteredReleases = filterByCommitish ? releases.filter( (r) => targetCommitishName === r.target_commitish.replace(headRefRegex, '') ) : releases + const filteredReleases = tagPrefix + ? commitishFilteredReleases.filter((r) => r.tag_name.startsWith(tagPrefix)) + : commitishFilteredReleases const sortedPublishedReleases = sortReleases( filteredReleases.filter((r) => !r.draft) ) @@ -129428,7 +129437,8 @@ const generateReleaseInfo = ({ // Use the first override parameter to identify // a version, from the most accurate to the least version || tag || name, - resolveVersionKeyIncrement(mergedPullRequests, config) + resolveVersionKeyIncrement(mergedPullRequests, config), + config['tag-prefix'] ) if (versionInfo) { @@ -129575,6 +129585,10 @@ const schema = (context) => { .allow('') .default(DEFAULT_CONFIG['name-template']), + 'tag-prefix': Joi.string() + .allow('') + .default(DEFAULT_CONFIG['tag-prefix']), + 'tag-template': Joi.string() .allow('') .default(DEFAULT_CONFIG['tag-template']), @@ -130077,24 +130091,30 @@ const toSemver = (version) => { return semver.coerce(version) } -const coerceVersion = (input) => { +const coerceVersion = (input, tagPrefix) => { if (!input) { return } + const stripTag = (input) => + tagPrefix && input.startsWith(tagPrefix) + ? input.slice(tagPrefix.length) + : input + return typeof input === 'object' - ? toSemver(input.tag_name) || toSemver(input.name) - : toSemver(input) + ? toSemver(stripTag(input.tag_name)) || toSemver(stripTag(input.name)) + : toSemver(stripTag(input)) } const getVersionInfo = ( release, template, inputVersion, - versionKeyIncrement + versionKeyIncrement, + tagPrefix ) => { - const version = coerceVersion(release) - inputVersion = coerceVersion(inputVersion) + const version = coerceVersion(release, tagPrefix) + inputVersion = coerceVersion(inputVersion, tagPrefix) if (!version && !inputVersion) { return defaultVersionInfo diff --git a/index.js b/index.js index a4babd9d7..e732ec663 100644 --- a/index.js +++ b/index.js @@ -159,12 +159,16 @@ module.exports = (app, { getRouter }) => { } const targetCommitish = commitish || config['commitish'] || ref - const filterByCommitish = config['filter-by-commitish'] + const { + 'filter-by-commitish': filterByCommitish, + 'tag-prefix': tagPrefix, + } = config const { draftRelease, lastRelease } = await findReleases({ context, targetCommitish, filterByCommitish, + tagPrefix, }) const { commits, pullRequests: mergedPullRequests } = diff --git a/lib/default-config.js b/lib/default-config.js index e3d8495e6..a27d66a41 100644 --- a/lib/default-config.js +++ b/lib/default-config.js @@ -3,6 +3,7 @@ const { SORT_BY, SORT_DIRECTIONS } = require('./sort-pull-requests') const DEFAULT_CONFIG = Object.freeze({ 'name-template': '', 'tag-template': '', + 'tag-prefix': '', 'change-template': `* $TITLE (#$NUMBER) @$AUTHOR`, 'change-title-escapes': '', 'no-changes-template': `* No changes`, diff --git a/lib/releases.js b/lib/releases.js index 2881311cc..cd58e9cb4 100644 --- a/lib/releases.js +++ b/lib/releases.js @@ -24,6 +24,7 @@ const findReleases = async ({ context, targetCommitish, filterByCommitish, + tagPrefix, }) => { let releaseCount = 0 let releases = await context.octokit.paginate( @@ -46,12 +47,15 @@ const findReleases = async ({ // `refs/heads/branch` and `branch` are the same thing in this context const headRefRegex = /^refs\/heads\// const targetCommitishName = targetCommitish.replace(headRefRegex, '') - const filteredReleases = filterByCommitish + const commitishFilteredReleases = filterByCommitish ? releases.filter( (r) => targetCommitishName === r.target_commitish.replace(headRefRegex, '') ) : releases + const filteredReleases = tagPrefix + ? commitishFilteredReleases.filter((r) => r.tag_name.startsWith(tagPrefix)) + : commitishFilteredReleases const sortedPublishedReleases = sortReleases( filteredReleases.filter((r) => !r.draft) ) @@ -318,7 +322,8 @@ const generateReleaseInfo = ({ // Use the first override parameter to identify // a version, from the most accurate to the least version || tag || name, - resolveVersionKeyIncrement(mergedPullRequests, config) + resolveVersionKeyIncrement(mergedPullRequests, config), + config['tag-prefix'] ) if (versionInfo) { diff --git a/lib/schema.js b/lib/schema.js index 521ba1b1a..ef134681d 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -39,6 +39,10 @@ const schema = (context) => { .allow('') .default(DEFAULT_CONFIG['name-template']), + 'tag-prefix': Joi.string() + .allow('') + .default(DEFAULT_CONFIG['tag-prefix']), + 'tag-template': Joi.string() .allow('') .default(DEFAULT_CONFIG['tag-template']), diff --git a/lib/versions.js b/lib/versions.js index abff55e1b..83c8dbd5b 100644 --- a/lib/versions.js +++ b/lib/versions.js @@ -136,24 +136,30 @@ const toSemver = (version) => { return semver.coerce(version) } -const coerceVersion = (input) => { +const coerceVersion = (input, tagPrefix) => { if (!input) { return } + const stripTag = (input) => + tagPrefix && input.startsWith(tagPrefix) + ? input.slice(tagPrefix.length) + : input + return typeof input === 'object' - ? toSemver(input.tag_name) || toSemver(input.name) - : toSemver(input) + ? toSemver(stripTag(input.tag_name)) || toSemver(stripTag(input.name)) + : toSemver(stripTag(input)) } const getVersionInfo = ( release, template, inputVersion, - versionKeyIncrement + versionKeyIncrement, + tagPrefix ) => { - const version = coerceVersion(release) - inputVersion = coerceVersion(inputVersion) + const version = coerceVersion(release, tagPrefix) + inputVersion = coerceVersion(inputVersion, tagPrefix) if (!version && !inputVersion) { return defaultVersionInfo diff --git a/schema.json b/schema.json index 0a4e6239e..86d977dee 100644 --- a/schema.json +++ b/schema.json @@ -47,6 +47,18 @@ } ] }, + "tag-prefix": { + "anyOf": [ + { + "type": "string", + "enum": [""] + }, + { + "default": "", + "type": "string" + } + ] + }, "tag-template": { "anyOf": [ { diff --git a/test/fixtures/config/config-with-tag-prefix.yml b/test/fixtures/config/config-with-tag-prefix.yml new file mode 100644 index 000000000..3d29de8b2 --- /dev/null +++ b/test/fixtures/config/config-with-tag-prefix.yml @@ -0,0 +1,7 @@ +name-template: 'static-tag-prefix-v$RESOLVED_VERSION 🌈' +tag-template: 'static-tag-prefix-v$RESOLVED_VERSION' +tag-prefix: static-tag-prefix-v +template: | + ## Previous release + + $PREVIOUS_TAG diff --git a/test/index.test.js b/test/index.test.js index 78da5ca41..896f355e4 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -2831,6 +2831,58 @@ describe('release-drafter', () => { }) }) + describe('with tag-prefix', () => { + it('gets the version from the tag, stripping the prefix', async () => { + getConfigMock('config-with-tag-prefix.yml') + // Explicitly include a RC suffix in order to differentiate the + // behaviour of semver.parse vs semver.coerce in versions.js + // + // We expect the release to be 2.1.4, not 2.1.5 + const alteredReleasePayload = { + ...releasePayload, + tag_name: 'static-tag-prefix-v2.1.4-RC3', + } + + nock('https://api.github.com') + .post('/graphql', (body) => + body.query.includes('query findCommitsWithAssociatedPullRequests') + ) + .reply(200, graphqlCommitsNoPRsPayload) + + nock('https://api.github.com') + .get('/repos/toolmantim/release-drafter-test-project/releases') + .query(true) + .reply(200, [alteredReleasePayload]) + .post( + '/repos/toolmantim/release-drafter-test-project/releases', + (body) => { + expect(body).toMatchInlineSnapshot(` + Object { + "body": "## Previous release + + static-tag-prefix-v2.1.4-RC3 + ", + "draft": true, + "name": "static-tag-prefix-v2.1.4 🌈", + "prerelease": false, + "tag_name": "static-tag-prefix-v2.1.4", + "target_commitish": "refs/heads/master", + } + `) + return true + } + ) + .reply(200, alteredReleasePayload) + + await probot.receive({ + name: 'push', + payload: pushPayload, + }) + + expect.assertions(1) + }) + }) + describe('with custom version resolver', () => { it('uses correct default when no labels exist', async () => { getConfigMock('config-with-custom-version-resolver-none.yml')