Skip to content

Commit 22af1c3

Browse files
committed
Changelog Github action
1 parent c971909 commit 22af1c3

File tree

5 files changed

+184
-110
lines changed

5 files changed

+184
-110
lines changed

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -14,33 +14,4 @@ Describe how you tested your changes. If possible and needed:
1414
This should be tested by QA
1515

1616
## Release Notes
17-
<!--
18-
If we definitely shouldn't add Release Notes, add only N/A.
19-
20-
Or enumerate sections, subsections and all changes.
21-
22-
Possible sections:
23-
- Highlights // major features
24-
- Known Issues // issues planned to be fixed, with possible workarounds
25-
- Breaking Changes // incompatible changes without deprecation cycle
26-
- Migration Notes // deprecations, removals, minimal version increases, defined behavior changes
27-
- Features // minor features
28-
- Fixes // bug fixes, undefined behavior changes
29-
30-
Possible subsections:
31-
- Multiple Platforms // any module, 2 or more platform changes
32-
- iOS // any module, iOS-only changes
33-
- Desktop // any module, Desktop-only changes
34-
- Web // any module, Web-only changes
35-
- Android // any module, Android-only changes
36-
- Resources // specific module, prefer it over the platform ones
37-
- Gradle Plugin // specific module, prefer it over the platform ones
38-
- Lifecycle // specific module, prefer it over the platform ones
39-
- Navigation // specific module, prefer it over the platform ones
40-
-->
41-
### Section - Subsection
42-
- Describe a change for adding it to https://github.com/JetBrains/compose-multiplatform/blob/master/CHANGELOG.md
43-
- _(prerelease fix)_ Fix some bug that introduced in a prerelease version (alpha/beta/rc). It will be included in a alpha/beta/rc changelog, but excluded from a stable changelog
44-
45-
### Section - Subsection
46-
- Describe another change if needed
17+
See the format in https://github.com/JetBrains/compose-multiplatform/blob/master/tools/changelog/PR_FORMAT.md
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Checks Release Notes in the PR description, runs when:
2+
# opened - PR is opened
3+
# edited - The title/description are changed
4+
# synchronize - New commits appeared.
5+
# We don't use the new content, but we still need to mark this commit Green/Red in GitHub UI
6+
7+
name: Check Release Notes in the description
8+
on:
9+
pull_request:
10+
types: [opened, edited, synchronize]
11+
12+
jobs:
13+
check:
14+
runs-on: ubuntu-24.04
15+
steps:
16+
- uses: actions/checkout@v4
17+
with:
18+
sparse-checkout: 'tools/changelog/check-release-notes-github-action'
19+
- uses: ./tools/changelog/check-release-notes-github-action
20+
with:
21+
checkout_ref: ${{ github.ref }}

tools/changelog/PR_FORMAT.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
Each Pull Request should contain "## Release Notes" section that describes changes in this PR.
2+
3+
Each release, changes from all PRs are combined into a list and added to CHANGELOG.md
4+
(by [changelog.main.kts](changelog.main.kts) script).
5+
6+
## Possible Release Notes
7+
8+
### No Release Notes
9+
```
10+
## Release Notes
11+
N/A
12+
```
13+
14+
### Simple Change
15+
```
16+
## Release Notes
17+
### Highlights - iOS
18+
- Describe a change
19+
```
20+
21+
### Simple Change with Details
22+
```
23+
## Release Notes
24+
### Highlights - iOS
25+
- Describe a change
26+
- Describe details 1
27+
- Describe details 2
28+
```
29+
30+
### Multiple Changes
31+
```
32+
## Release Notes
33+
### Features - Desktop
34+
- Describe change 1
35+
- Describe details
36+
- Describe change 2
37+
38+
### Fixes - Web
39+
- Describe change 3
40+
```
41+
42+
### Prerelease Fix
43+
Will be includede in alpha/beta/rc changelog, excluded from stable.
44+
```
45+
## Release Notes
46+
### Fixes - Multiple Platforms
47+
- _(prerelease fix)_ Fixed CPU overheating on pressing Shift appeared in 1.8.0-alpha02
48+
```
49+
50+
## Possible Sections
51+
52+
### Sections
53+
<!-- This is parsed by changelog.main.kts -->
54+
```
55+
- Highlights # major features
56+
- Known Issues # issues planned to be fixed, with possible workarounds
57+
- Breaking Changes # incompatible changes without deprecation cycle
58+
- Migration Notes # deprecations, removals, minimal version increases, defined behavior changes
59+
- Features # minor features
60+
- Fixes # bug fixes, undefined behavior changes
61+
```
62+
63+
### Subsections
64+
<!-- This is parsed by changelog.main.kts -->
65+
```
66+
- Multiple Platforms # any module, 2 or more platform changes
67+
- iOS # any module, iOS-only changes
68+
- Desktop # any module, Desktop-only changes
69+
- Web # any module, Web-only changes
70+
- Android # any module, Android-only changes
71+
- Resources # specific module, prefer it over the platform ones
72+
- Gradle Plugin # specific module, prefer it over the platform ones
73+
- Lifecycle # specific module, prefer it over the platform ones
74+
- Navigation # specific module, prefer it over the platform ones
75+
```
Lines changed: 61 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515
*
1616
* Changelog entries are generated from reading Release Notes in GitHub PR's.
1717
*
18-
* ## Checking PRs
18+
* ## Checking PR description in a file
19+
* Not supposed to be called manually, used by GitHub workflow:
20+
* https://github.com/JetBrains/compose-multiplatform/blob/master/tools/changelog/check-release-notes-github-action/action.yml)
1921
* ```
20-
* kotlin changelog.main.kts action=checkPr compose-multiplatform 5202
22+
* kotlin changelog.main.kts action=checkPr prDescription.txt
2123
* ```
2224
*
2325
* compose-multiplatform - name of the GitHub repo
@@ -41,8 +43,10 @@
4143

4244
import com.google.gson.Gson
4345
import com.google.gson.annotations.SerializedName
46+
import java.io.File
4447
import java.io.IOException
4548
import java.lang.ProcessBuilder.Redirect
49+
import java.lang.System.err
4650
import java.net.URL
4751
import java.net.URLEncoder
4852
import java.nio.charset.StandardCharsets.UTF_8
@@ -51,35 +55,9 @@ import java.time.format.DateTimeFormatter
5155
import java.util.*
5256
import kotlin.system.exitProcess
5357

54-
//region ========================================== CONSTANTS =========================================
55-
56-
// Sections from the template https://github.com/JetBrains/compose-multiplatform/blob/master/.github/PULL_REQUEST_TEMPLATE.md?plain=1
57-
// Changelog should contain only these categories
58-
59-
val standardSections = listOf(
60-
"Highlights",
61-
"Known Issues",
62-
"Breaking Changes",
63-
"Migration Notes",
64-
"Features",
65-
"Fixes",
66-
)
67-
68-
val standardSubsections = listOf(
69-
"Multiple Platforms",
70-
"iOS",
71-
"Desktop",
72-
"Web",
73-
"Android",
74-
"Resources",
75-
"Gradle Plugin",
76-
"Lifecycle",
77-
"Navigation",
78-
)
79-
8058
val changelogFile = __FILE__.resolve("../../CHANGELOG.md").canonicalFile
81-
82-
//endregion
59+
val prFormatFile = File("PR_FORMAT.md")
60+
val prFormatLink = "https://github.com/JetBrains/compose-multiplatform/blob/master/tools/changelog/PR_FORMAT.md"
8361

8462
val argsKeyless = args
8563
.filter { !it.contains("=") }
@@ -89,9 +67,19 @@ val argsKeyToValue = args
8967
.associate { it.substringBefore("=") to it.substringAfter("=") }
9068

9169
val token = argsKeyToValue["token"]
92-
if (token == null) {
93-
println("To increase the rate limit, specify token (https://github.com/settings/tokens), adding token=yourtoken in the end")
94-
}
70+
71+
// Parse sections from [PR_FORMAT.md]
72+
fun parseSections(title: String) = prFormatFile
73+
.readText()
74+
.substringAfter(title)
75+
.substringAfter("```")
76+
.substringBefore("```")
77+
.split("\n")
78+
.map { it.trim().removePrefix("-").substringBefore("#").substringBefore("\n").trim() }
79+
.filter { it.isNotEmpty() }
80+
81+
val standardSections = parseSections("### Sections")
82+
val standardSubsections = parseSections("### Subsections")
9583

9684
println()
9785

@@ -101,6 +89,10 @@ when (argsKeyToValue["action"]) {
10189
}
10290

10391
fun generateChangelog() {
92+
if (token == null) {
93+
println("To increase the rate limit, specify token (https://github.com/settings/tokens), adding token=yourtoken in the end")
94+
}
95+
10496
val commitsArg = argsKeyless.getOrNull(0) ?: "HEAD"
10597

10698
var previousVersionCommitArg: String?
@@ -253,55 +245,42 @@ fun generateChangelog() {
253245
}
254246

255247
fun checkPr() {
256-
val repo = argsKeyless.getOrNull(0) ?: error("Please specify PR number as the first argument")
257-
val prNumber = argsKeyless.getOrNull(1) ?: error("Please specify PR number as the second argument")
258-
val releaseNotes = pullRequest(repo, prNumber).extractReleaseNotes()
248+
val filePath = argsKeyless.getOrNull(0) ?: error("Please specify a file that contains PR description as the first argument")
259249

260-
val commonDescription = """
261-
Valid examples:
262-
263-
## Release Notes
264-
### Features - Desktop
265-
- Added Drag&Drop support
266-
- Fixed non-working Drag&Drop in dialogs
267-
268-
### Features - iOS
269-
- Added Drag&Drop support
250+
val body = File(filePath).readText()
251+
val releaseNotes = extractReleaseNotes(body, 0, "https://github.com/JetBrains/compose-multiplatform/pull/0")
270252

271-
## Release Notes
272-
N/A
273-
274-
Allowed sections: ${standardSections.joinToString(", ")}
275-
Allowed subsections: ${standardSubsections.joinToString(", ")}
276-
""".trimIndent()
277-
278-
val nonstandardSections = if (releaseNotes is ReleaseNotes.Specified) {
279-
releaseNotes.entries
280-
.filter { it.section !in standardSections || it.subsection !in standardSubsections }
281-
.map { "${it.section} - ${it.subsection}" }
282-
.toSet()
283-
} else {
284-
emptySet()
285-
}
253+
val nonstandardSections = releaseNotes?.entries
254+
.orEmpty()
255+
.filter { it.section !in standardSections || it.subsection !in standardSubsections }
256+
.map { "${it.section} - ${it.subsection}" }
257+
.toSet()
258+
259+
println()
286260

287261
when {
288-
releaseNotes == null -> {
289-
System.err.println("""
290-
"## Release Notes" section is missing in the PR description
291-
292-
293-
""".trimIndent() + commonDescription)
262+
releaseNotes is ReleaseNotes.Specified && releaseNotes.entries.isEmpty() -> {
263+
err.println("\"## Release Notes\" doesn't contain any items, or section isn't specified")
264+
err.println()
265+
err.println("See the format in $prFormatLink")
266+
exitProcess(1)
267+
}
268+
releaseNotes is ReleaseNotes.Specified && nonstandardSections.isNotEmpty() -> {
269+
err.println("\"## Release Notes\" contains nonstandard sections:")
270+
err.println(nonstandardSections.joinToString(", "))
271+
err.println()
272+
err.println("See possible sections in $prFormatLink#possible-sections")
294273
exitProcess(1)
295274
}
296-
nonstandardSections.isNotEmpty() -> {
297-
System.err.println("""
298-
"## Release Notes" contains nonstandard sections:
299-
${nonstandardSections.joinToString(", ")}
300-
301-
302-
""".trimIndent() + commonDescription)
275+
releaseNotes == null -> {
276+
err.println("\"## Release Notes\" section is missing in the PR description")
277+
err.println()
278+
err.println("See the format in $prFormatLink")
303279
exitProcess(1)
304280
}
281+
else -> {
282+
println("\"## Release Notes\" are correct")
283+
}
305284
}
306285
}
307286

@@ -311,9 +290,9 @@ fun checkPr() {
311290
fun currentChangelogDate() = LocalDate.now().format(DateTimeFormatter.ofPattern("MMMM yyyy", Locale.ENGLISH))
312291

313292
/**
314-
* Extract by format https://github.com/JetBrains/compose-multiplatform/blob/master/.github/PULL_REQUEST_TEMPLATE.md?plain=1
293+
* Extract by format [PR_FORMAT.md]
315294
*/
316-
fun GitHubPullEntry.extractReleaseNotes(): ReleaseNotes? {
295+
fun extractReleaseNotes(body: String?, prNumber: Int, prLink: String): ReleaseNotes? {
317296
fun String?.substringBetween(begin: String, end: String): String? {
318297
val after = this?.substringAfter(begin, "")?.ifBlank { null }
319298
return after?.substringBefore(end, "")?.ifBlank { null } ?: after
@@ -369,8 +348,8 @@ fun GitHubPullEntry.extractReleaseNotes(): ReleaseNotes? {
369348
lineFixed,
370349
section,
371350
subsection,
372-
number,
373-
htmlUrl.takeIf { isTopLevel }
351+
prNumber,
352+
prLink.takeIf { isTopLevel }
374353
)
375354
)
376355
isFirstLine = false
@@ -396,8 +375,10 @@ fun entriesForRepo(repo: String, firstCommit: String, lastCommit: String): List<
396375
pullRequest: GitHubPullEntry?
397376
): List<ChangelogEntry> {
398377
return if (pullRequest != null) {
399-
pullRequest.extractReleaseNotes()?.entries ?:
400-
listOf(ChangelogEntry("- ${pullRequest.title}", null, null, pullRequest.number, pullRequest.htmlUrl))
378+
with(pullRequest) {
379+
extractReleaseNotes(body, number, htmlUrl)?.entries ?:
380+
listOf(ChangelogEntry("- $title", null, null, number, htmlUrl))
381+
}
401382
} else {
402383
listOf()
403384
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Reusable GitHub action needed to check Release Notes in the PR description. Used by multiple Compose repositories.
2+
3+
name: Check Release Notes in the description
4+
5+
inputs:
6+
checkout_ref:
7+
type: string
8+
default: 'master'
9+
10+
runs:
11+
using: "composite"
12+
steps:
13+
- uses: actions/checkout@v4
14+
with:
15+
repository: JetBrains/compose-multiplatform
16+
ref: ${{ inputs.checkout_ref }}
17+
sparse-checkout: 'tools/changelog'
18+
- name: Check Release Notes in the description
19+
shell: bash
20+
env:
21+
# To avoid injections as suggested in https://docs.github.com/en/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable
22+
PR_DESCRIPTION: ${{ github.event.pull_request.body }}
23+
run: |
24+
cd tools/changelog
25+
echo "$PR_DESCRIPTION" > prDescription.txt
26+
kotlin changelog.main.kts action=checkPr prDescription.txt token=""

0 commit comments

Comments
 (0)