Skip to content

Commit cfec7ce

Browse files
feat: deprecate include-pre-releases in favor of prerelease (#1515)
* Fix draft release detection to match includePreReleases semantics (#1425) The `findReleases` function filtered draft releases using `r.prerelease === includePreReleases`, which is incorrect because `include-pre-releases` controls whether prereleases count when searching for the last release, not whether only prerelease drafts should match. This caused users with `include-pre-releases: true` to never find their existing non-prerelease drafts (`false === true` is always false), creating duplicate draft releases on every push. Fix by using the same filter pattern already used for last release detection: `(!r.prerelease || includePreReleases)`. This means: - `includePreReleases = false` → only match non-prerelease drafts - `includePreReleases = true` → match any draft (prerelease or not) Fixes #1425 * Fix test to match suite name: use actual prerelease draft in assertion The test 'should return prerelease draft when includePreReleases is true' previously expected a non-prerelease draft (prerelease: false), contradicting the suite name. Reworked mock data so the test finds a prerelease draft, matching what the test name describes. * Match draft release by prerelease status of the release being created Instead of using includePreReleases (which controls the lastRelease scan) to filter drafts, use the config's prerelease flag (isPreRelease) to find the draft whose prerelease status matches what we're about to create. This correctly solves both #1425 and the original #1385 problem: - When drafting a stable release, we find the non-prerelease draft - When drafting a prerelease, we prefer the prerelease draft - Falls back to a non-prerelease draft if no prerelease draft exists yet (handles the first prerelease run before the draft is marked) * feat: deprecate include-pre-releases and document new behavior of prerelease --------- Co-authored-by: Clément Chanchevrier <[email protected]>
1 parent 4234cae commit cfec7ce

6 files changed

Lines changed: 229 additions & 56 deletions

File tree

README.md

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -135,12 +135,12 @@ You can configure Release Drafter using the following key in your `.github/relea
135135
| `exclude-labels` | Optional | Exclude pull requests using labels. Refer to [Exclude Pull Requests](#exclude-pull-requests) to learn more about this option. |
136136
| `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. |
137137
| `exclude-contributors` | Optional | Exclude specific usernames from the generated `$CONTRIBUTORS` variable. Refer to [Exclude Contributors](#exclude-contributors) to learn more about this option. |
138-
| `include-pre-releases` | Optional | Include pre releases as "full" releases when drafting release notes. Default: `false`. |
139138
| `no-contributors-template` | Optional | The template to use for `$CONTRIBUTORS` when there's no contributors to list. Default: `"No contributors"`. |
140139
| `replacers` | Optional | Search and replace content in the generated changelog body. Refer to [Replacers](#replacers) to learn more about this option. |
141140
| `sort-by` | Optional | Sort changelog by merged_at or title. Can be one of: `merged_at`, `title`. Default: `merged_at`. |
142141
| `sort-direction` | Optional | Sort changelog in ascending or descending order. Can be one of: `ascending`, `descending`. Default: `descending`. |
143-
| `prerelease` | Optional | Mark the draft release as pre-release. Default `false`. |
142+
| `prerelease` | Optional | Whether to draft a prerelease, with changes since another prerelease (if applicable). Default `false`. |
143+
| `prerelease-identifier` | Optional | A string indicating an identifier (alpha, beta, rc, etc), to increment the prerelease version. This automatically enables `prerelease` if not already set to `true`. Default `''`. |
144144
| `latest` | Optional | Mark the release as latest. Only works for published releases. Can be one of: `true`, `false`, `legacy`. Default `true`. |
145145
| `version-resolver` | Optional | Adjust the `$RESOLVED_VERSION` variable using labels. Refer to [Version Resolver](#version-resolver) to learn more about this |
146146
| `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`. |
@@ -344,16 +344,52 @@ autolabeler:
344344
- '/JIRA-[0-9]{1,4}/'
345345
```
346346

347-
## Prerelease increment
347+
## Prerelease workflow
348348

349-
When creating prerelease (`prerelease: true`), you can add a prerelease identifier to increment the prerelease version number, with the `prerelease-identifier` option. It accept any string, but it's recommended to use [Semantic Versioning](https://semver.org/) prerelease identifiers (alpha, beta, rc, etc).
349+
Release draft supports working with prereleases. It expects your workflow to be :
350350

351-
Using `prerelease-identifier` automatically enable `include-prereleases`.
351+
- A stable release is published, ex: `v3.5.0`
352+
- You merge or add meaningful changes your users may want to see, but you are not quite ready for production
353+
- You publish a prerelease, ex: `v3.5.0-rc.1`
354+
- You merge more changes
355+
- You publish another prerelease, ex: `v3.5.0-rc.2`
356+
- You decide code is ready for production, you publish `v3.5.1` (or another increment based on your changes)
352357

353-
```yml
354-
prerelease-identifier: 'alpha' # will create a prerelease with version number x.x.x-alpha.x
358+
With release-drafter, you can draft each of these releases and prereleases with the appropriate content using parameter '`prerelease`' and '`prerelease-identifier`' - available as either an input of from the config-file.
359+
360+
```yaml
361+
jobs:
362+
update_full_release_draft:
363+
runs-on: ubuntu-latest
364+
steps:
365+
- uses: release-drafter/release-drafter@v6
366+
with:
367+
prerelease: false # the default
368+
# ... rest of your config
369+
update_prerelease_draft:
370+
runs-on: ubuntu-latest
371+
steps:
372+
- uses: release-drafter/release-drafter@v6
373+
with:
374+
prerelease: true
375+
prerelease-identifier: 'rc' # Use semver identifiers : alpha, beta, rc, etc
355376
```
356377

378+
Here, both jobs run in parallel every time you add changes to the configured branch.
379+
380+
- `update_full_release_draft` will pile-up changes since `v3.5.0` inside a draft for `v3.5.1` (or `v3.6.0` or `v4.0.0`, depending on your config)
381+
- `update_prerelease_draft` will pile-up changes since the last prerelease in a prerelease-draft. Changes are :
382+
- if no previous (published) prereleases are found - changes since `v3.5.0` in a draft for `v3.5.0-rc.1` (prerelease-draft)
383+
- or if `v3.5.0-rc.1` exists (published) already - changes since `v3.5.0-rc.1` in a draft for `v3.5.0-rc.2` (prerelease-draft)
384+
385+
Some users like to run `update_prerelease_draft` with `publish: true`, such as prereleases are published immediately without the need for human intervention (or an external automation). Since prereleases are not meant to be stable in the first place, automation may be an acceptable risk for you too.
386+
387+
> [!IMPORTANT]
388+
>
389+
> - The `include-pre-releases` config was deprecated in `v6.3.0`. See [#1515](https://github.com/release-drafter/release-drafter/pull/1515)
390+
> - `prerelease-identifier` is not required when `prerelease` is enabled, but your prerelease will be named after / be associated with a tag that is not semver-compliant to actual prereleases.
391+
> - when specified `prerelease-identifier` enables `prerelease: true`
392+
357393
## Projects that don't use Semantic Versioning
358394

359395
If your project doesn't follow [Semantic Versioning](https://semver.org) you can still use Release Drafter, but you may want to set the `version-template` option to customize how the `$NEXT_{PATCH,MINOR,MAJOR}_VERSION` environment variables are generated.
@@ -371,8 +407,8 @@ The Release Drafter GitHub Action accepts a number of optional inputs directly i
371407
| `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. |
372408
| `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. |
373409
| `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). |
374-
| `prerelease` | A boolean indicating whether the release being created or updated is a prerelease. |
375-
| `prerelease-identifier` | A string indicating an identifier (alpha, beta, rc, etc), to increment the prerelease version. number |
410+
| `prerelease` | Whether to draft a prerelease, with changes since another prerelease (if applicable). Default `false`. |
411+
| `prerelease-identifier` | A string indicating an identifier (alpha, beta, rc, etc), to increment the prerelease version. This automatically enables `prerelease` if not already set to `true`. Default `''`. |
376412
| `latest` | A string indicating whether the release being created or updated should be marked as latest. |
377413
| `commitish` | A string specifying the target branch for the release being created. |
378414
| `header` | A string that would be added before the template body. |

dist/index.js

Lines changed: 53 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -189902,21 +189902,17 @@ module.exports = (app, { getRouter }) => {
189902189902
const {
189903189903
'filter-by-commitish': filterByCommitish,
189904189904
'include-pre-releases': includePreReleases,
189905-
'prerelease-identifier': preReleaseIdentifier,
189906189905
'tag-prefix': tagPrefix,
189907189906
latest,
189908189907
prerelease,
189909189908
} = config
189910189909

189911-
const shouldIncludePreReleases = Boolean(
189912-
includePreReleases || preReleaseIdentifier
189913-
)
189914-
189915189910
const { draftRelease, lastRelease } = await findReleases({
189916189911
context,
189917189912
targetCommitish,
189918189913
filterByCommitish,
189919-
includePreReleases: shouldIncludePreReleases,
189914+
includePreReleases,
189915+
isPreRelease: prerelease,
189920189916
tagPrefix,
189921189917
})
189922189918

@@ -190029,6 +190025,10 @@ function updateConfigFromInput(config, input) {
190029190025
config['prerelease-identifier'] = input.preReleaseIdentifier
190030190026
}
190031190027

190028+
if (!config.prerelease && config['prerelease-identifier']) {
190029+
config.prerelease = true
190030+
}
190031+
190032190032
config.latest = config.prerelease
190033190033
? 'false'
190034190034
: input.latest || config.latest || undefined
@@ -190541,9 +190541,13 @@ const findReleases = async ({
190541190541
targetCommitish,
190542190542
filterByCommitish,
190543190543
includePreReleases,
190544+
isPreRelease,
190544190545
tagPrefix,
190545190546
}) => {
190546190547
let releaseCount = 0
190548+
/**
190549+
* @type {object[]}
190550+
*/
190547190551
let releases = await context.octokit.paginate(
190548190552
context.octokit.repos.listReleases.endpoint.merge(
190549190553
context.repo({
@@ -190561,8 +190565,8 @@ const findReleases = async ({
190561190565

190562190566
log({ context, message: `Found ${releases.length} releases` })
190563190567

190564-
// `refs/heads/branch` and `branch` are the same thing in this context
190565-
const headRefRegex = /^refs\/heads\//
190568+
// Filter releases
190569+
const headRefRegex = /^refs\/heads\// // `refs/heads/branch` and `branch` are the same thing in this context
190566190570
const targetCommitishName = targetCommitish.replace(headRefRegex, '')
190567190571
const commitishFilteredReleases = filterByCommitish
190568190572
? releases.filter(
@@ -190573,18 +190577,42 @@ const findReleases = async ({
190573190577
const filteredReleases = tagPrefix
190574190578
? commitishFilteredReleases.filter((r) => r.tag_name.startsWith(tagPrefix))
190575190579
: commitishFilteredReleases
190576-
const sortedSelectedReleases = sortReleases(
190577-
filteredReleases.filter(
190578-
(r) => !r.draft && (!r.prerelease || includePreReleases)
190579-
),
190580-
tagPrefix
190580+
190581+
// Split drafts and published releases
190582+
let publishedReleases = filteredReleases.filter((r) => !r.draft)
190583+
let draftReleases = filteredReleases.filter((r) => r.draft)
190584+
190585+
// Handle prereleases
190586+
publishedReleases = publishedReleases.filter(
190587+
(publishedRelease) =>
190588+
isPreRelease || includePreReleases // `includePreReleases` will be removed in future versions
190589+
? publishedRelease.prerelease || !publishedRelease.prerelease // Both prerelease and regular published-releases
190590+
: !publishedRelease.prerelease // Only regular published-releases
190581190591
)
190582-
const draftRelease = filteredReleases.find(
190583-
(r) => r.draft && r.prerelease === includePreReleases
190592+
draftReleases = draftReleases.filter(
190593+
(draftRelease) =>
190594+
isPreRelease
190595+
? draftRelease.prerelease // Only pre-releases drafts
190596+
: !draftRelease.prerelease // Only regular drafts
190584190597
)
190585-
const lastRelease = sortedSelectedReleases[sortedSelectedReleases.length - 1]
190598+
190599+
// Sort results
190600+
const draftRelease = draftReleases[0] // Should this be sorted ?
190601+
const lastRelease = sortReleases(publishedReleases, tagPrefix)?.at(-1)
190586190602

190587190603
if (draftRelease) {
190604+
if (draftReleases.length > 1) {
190605+
log({
190606+
context,
190607+
message: `Multiple draft releases found : ${draftReleases
190608+
.map((r) => r.tag_name)
190609+
.join(', ')}`,
190610+
})
190611+
log({
190612+
context,
190613+
message: `Returning the first one (octokit response order)`,
190614+
})
190615+
}
190588190616
log({ context, message: `Draft release: ${draftRelease.tag_name}` })
190589190617
} else {
190590190618
log({ context, message: `No draft release found` })
@@ -190593,9 +190621,9 @@ const findReleases = async ({
190593190621
if (lastRelease) {
190594190622
log({
190595190623
context,
190596-
message: `Last release${
190597-
includePreReleases ? ' (including prerelease)' : ''
190598-
}: ${lastRelease.tag_name}`,
190624+
message: `Last release${isPreRelease ? ' (including prerelease)' : ''}: ${
190625+
lastRelease.tag_name
190626+
}`,
190599190627
})
190600190628
} else {
190601190629
log({ context, message: `No last release found` })
@@ -191299,6 +191327,12 @@ const validateSchema = (context, repoConfig) => {
191299191327
config.autolabeler = []
191300191328
}
191301191329

191330+
if (config['include-pre-releases']) {
191331+
context.log.info(
191332+
"'include-pre-releases' will be deprecated in next version. Use 'prerelease: true' instead. See PR #1515 for more"
191333+
)
191334+
}
191335+
191302191336
return config
191303191337
}
191304191338

index.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -155,21 +155,17 @@ module.exports = (app, { getRouter }) => {
155155
const {
156156
'filter-by-commitish': filterByCommitish,
157157
'include-pre-releases': includePreReleases,
158-
'prerelease-identifier': preReleaseIdentifier,
159158
'tag-prefix': tagPrefix,
160159
latest,
161160
prerelease,
162161
} = config
163162

164-
const shouldIncludePreReleases = Boolean(
165-
includePreReleases || preReleaseIdentifier
166-
)
167-
168163
const { draftRelease, lastRelease } = await findReleases({
169164
context,
170165
targetCommitish,
171166
filterByCommitish,
172-
includePreReleases: shouldIncludePreReleases,
167+
includePreReleases,
168+
isPreRelease: prerelease,
173169
tagPrefix,
174170
})
175171

@@ -282,6 +278,10 @@ function updateConfigFromInput(config, input) {
282278
config['prerelease-identifier'] = input.preReleaseIdentifier
283279
}
284280

281+
if (!config.prerelease && config['prerelease-identifier']) {
282+
config.prerelease = true
283+
}
284+
285285
config.latest = config.prerelease
286286
? 'false'
287287
: input.latest || config.latest || undefined

lib/releases.js

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,13 @@ const findReleases = async ({
2929
targetCommitish,
3030
filterByCommitish,
3131
includePreReleases,
32+
isPreRelease,
3233
tagPrefix,
3334
}) => {
3435
let releaseCount = 0
36+
/**
37+
* @type {object[]}
38+
*/
3539
let releases = await context.octokit.paginate(
3640
context.octokit.repos.listReleases.endpoint.merge(
3741
context.repo({
@@ -49,8 +53,8 @@ const findReleases = async ({
4953

5054
log({ context, message: `Found ${releases.length} releases` })
5155

52-
// `refs/heads/branch` and `branch` are the same thing in this context
53-
const headRefRegex = /^refs\/heads\//
56+
// Filter releases
57+
const headRefRegex = /^refs\/heads\// // `refs/heads/branch` and `branch` are the same thing in this context
5458
const targetCommitishName = targetCommitish.replace(headRefRegex, '')
5559
const commitishFilteredReleases = filterByCommitish
5660
? releases.filter(
@@ -61,18 +65,42 @@ const findReleases = async ({
6165
const filteredReleases = tagPrefix
6266
? commitishFilteredReleases.filter((r) => r.tag_name.startsWith(tagPrefix))
6367
: commitishFilteredReleases
64-
const sortedSelectedReleases = sortReleases(
65-
filteredReleases.filter(
66-
(r) => !r.draft && (!r.prerelease || includePreReleases)
67-
),
68-
tagPrefix
68+
69+
// Split drafts and published releases
70+
let publishedReleases = filteredReleases.filter((r) => !r.draft)
71+
let draftReleases = filteredReleases.filter((r) => r.draft)
72+
73+
// Handle prereleases
74+
publishedReleases = publishedReleases.filter(
75+
(publishedRelease) =>
76+
isPreRelease || includePreReleases // `includePreReleases` will be removed in future versions
77+
? publishedRelease.prerelease || !publishedRelease.prerelease // Both prerelease and regular published-releases
78+
: !publishedRelease.prerelease // Only regular published-releases
6979
)
70-
const draftRelease = filteredReleases.find(
71-
(r) => r.draft && r.prerelease === includePreReleases
80+
draftReleases = draftReleases.filter(
81+
(draftRelease) =>
82+
isPreRelease
83+
? draftRelease.prerelease // Only pre-releases drafts
84+
: !draftRelease.prerelease // Only regular drafts
7285
)
73-
const lastRelease = sortedSelectedReleases[sortedSelectedReleases.length - 1]
86+
87+
// Sort results
88+
const draftRelease = draftReleases[0] // Should this be sorted ?
89+
const lastRelease = sortReleases(publishedReleases, tagPrefix)?.at(-1)
7490

7591
if (draftRelease) {
92+
if (draftReleases.length > 1) {
93+
log({
94+
context,
95+
message: `Multiple draft releases found : ${draftReleases
96+
.map((r) => r.tag_name)
97+
.join(', ')}`,
98+
})
99+
log({
100+
context,
101+
message: `Returning the first one (octokit response order)`,
102+
})
103+
}
76104
log({ context, message: `Draft release: ${draftRelease.tag_name}` })
77105
} else {
78106
log({ context, message: `No draft release found` })
@@ -81,9 +109,9 @@ const findReleases = async ({
81109
if (lastRelease) {
82110
log({
83111
context,
84-
message: `Last release${
85-
includePreReleases ? ' (including prerelease)' : ''
86-
}: ${lastRelease.tag_name}`,
112+
message: `Last release${isPreRelease ? ' (including prerelease)' : ''}: ${
113+
lastRelease.tag_name
114+
}`,
87115
})
88116
} else {
89117
log({ context, message: `No last release found` })

lib/schema.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,12 @@ const validateSchema = (context, repoConfig) => {
223223
config.autolabeler = []
224224
}
225225

226+
if (config['include-pre-releases']) {
227+
context.log.info(
228+
"'include-pre-releases' will be deprecated in next version. Use 'prerelease: true' instead. See PR #1515 for more"
229+
)
230+
}
231+
226232
return config
227233
}
228234

0 commit comments

Comments
 (0)