Skip to content

Commit d5c44a0

Browse files
authored
Merge pull request #394 from greshny-forks/releases-script
release notes script
2 parents 92a449d + ccfa166 commit d5c44a0

File tree

3 files changed

+216
-4
lines changed

3 files changed

+216
-4
lines changed

.github/workflows/test.yml

+7-4
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
runs-on: ${{ matrix.os }}
1919
steps:
2020
- name: Checkout
21-
uses: actions/checkout@v2
21+
uses: actions/checkout@v4
2222

2323
# Container modules occupy too much disk space. The GitHub-hosted runners ran into the
2424
# error: "no space left on device."
@@ -36,12 +36,15 @@ jobs:
3636
swap-storage: false
3737

3838
- name: Setup Java
39-
uses: actions/setup-java@v2
39+
uses: actions/setup-java@v4
4040
with:
4141
distribution: temurin # See 'Supported distributions' for available options
4242
java-version: ${{ matrix.java }}
4343
check-latest: true
44-
44+
cache: 'sbt'
45+
- uses: sbt/setup-sbt@v1
46+
with:
47+
sbt-runner-version: 1.10.5
4548
- name: Restore cache
4649
uses: actions/cache@v2
4750
with:
@@ -53,4 +56,4 @@ jobs:
5356
restore-keys: |
5457
cache-v1-
5558
- name: Compile and Test
56-
run: sbt +test
59+
run: sbt +test

CONTRIBUTING.md

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# CONTRIBUTING
2+
3+
## How to release
4+
5+
### Prerequisites
6+
- Install [sdkman](https://sdkman.io/install)
7+
- Install latest scala, e.g. `sdk install scala 3.6.2`
8+
9+
### Generate release notes
10+
11+
- Bump the version in `version.sbt`
12+
- Run `git tag v<NEW_VERSION>` (e.g., `git tag v0.42.3`)
13+
- release notes with `scala bin/generate-release.scala -- release-notes -s v<OLD_VERSION> -e v<NEW_VERSION> --token GITHUB_TOKEN` (e.g., `scala bin/generate-release.scala -- release-notes -s v0.42.2 -e v0.42.3 --token GITHUB_TOKEN`).
14+

bin/generate-release.scala

+195
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
//> using scala 3.6.2
2+
//> using dep "com.lihaoyi::os-lib:0.11.3"
3+
//> using dep "com.monovore::decline:2.4.1"
4+
//> using dep "org.kohsuke:github-api:1.326"
5+
6+
import scala.jdk.CollectionConverters._
7+
import scala.collection.mutable.ListBuffer
8+
import scala.collection.mutable
9+
import org.kohsuke.github.GitHubBuilder
10+
import com.monovore.decline.*
11+
import cats.syntax.all.given
12+
13+
import java.util.Date
14+
import java.text.SimpleDateFormat
15+
16+
object ReleaseNotes {
17+
val Organization = "testcontainers"
18+
val Repo = "testcontainers-scala"
19+
20+
val firstTagOpt =
21+
Opts.option[String](long = "first-tag", help = "start git tag", short = "s")
22+
23+
val lastTagOpt =
24+
Opts.option[String](long = "last-tag", help = "end git tag", short = "e")
25+
26+
val githubTokenOpt =
27+
Opts.option[String](long = "token", help = "github token", short = "t")
28+
val debugOpt = Opts
29+
.flag(long = "debug", help = "prints debug information", short = "d")
30+
.orFalse
31+
32+
final case class Arguments(
33+
firstTag: String,
34+
lastTag: String,
35+
githubToken: String,
36+
debug: Boolean
37+
)
38+
39+
val opts: Opts[Arguments] =
40+
Opts.subcommand("release-notes", "generates release notes") {
41+
(firstTagOpt, lastTagOpt, githubTokenOpt, debugOpt).mapN(Arguments.apply)
42+
}
43+
44+
def run(args: Arguments): Unit = {
45+
def debug(msg: String) = if (args.debug)
46+
println(msg)
47+
48+
val commits = os
49+
.proc(List("git", "rev-list", s"${args.firstTag}..${args.lastTag}"))
50+
.call()
51+
.out
52+
.trim()
53+
.linesIterator
54+
.size
55+
56+
debug(
57+
s"Number of commits between ${args.firstTag}..${args.lastTag}: $commits"
58+
)
59+
60+
val contributors = os
61+
.proc(
62+
List(
63+
"git",
64+
"shortlog",
65+
"-sn",
66+
"--no-merges",
67+
s"${args.firstTag}..${args.lastTag}"
68+
)
69+
)
70+
.call()
71+
.out
72+
.trim()
73+
.linesIterator
74+
.toList
75+
76+
val command = List(
77+
"git",
78+
"log",
79+
s"${args.firstTag}..${args.lastTag}",
80+
"--first-parent",
81+
"master",
82+
"--pretty=format:%H"
83+
)
84+
85+
val output = os.proc(command).call().out.trim()
86+
87+
val gh = new GitHubBuilder()
88+
.withOAuthToken(args.githubToken)
89+
.withEndpoint("https://api.github.com")
90+
.build()
91+
92+
val foundPRs = mutable.Set.empty[Int]
93+
val mergedPRs = ListBuffer[String]()
94+
95+
for {
96+
// group in order to optimize API
97+
searchSha <-
98+
output.split('\n').grouped(5).map(_.mkString("SHA ", " SHA ", ""))
99+
allMatching =
100+
gh.searchIssues()
101+
.q(s"repo:$Organization/$Repo type:pr $searchSha")
102+
.list()
103+
pr <- allMatching.toList().asScala.sortBy(_.getClosedAt()).reverse
104+
prNumber = pr.getNumber()
105+
if !foundPRs(prNumber)
106+
} {
107+
foundPRs += prNumber
108+
val login = pr.getUser().getLogin()
109+
val formattedPR =
110+
s"- ${pr.getTitle()} [\\#${pr.getNumber()}](${pr.getHtmlUrl()}) ([$login](https://github.com/$login))"
111+
mergedPRs += formattedPR
112+
}
113+
114+
val releaseNotes =
115+
template(
116+
firstTag = args.firstTag,
117+
lastTag = args.lastTag,
118+
mergedPrs = mergedPRs.toList,
119+
commits = commits,
120+
contributors = contributors
121+
)
122+
123+
debug(releaseNotes)
124+
125+
gh.getRepository(s"$Organization/$Repo")
126+
.createRelease(args.lastTag)
127+
.name(s"${args.lastTag}")
128+
.body(releaseNotes)
129+
.create()
130+
}
131+
132+
def today: String = {
133+
val formatter = new SimpleDateFormat("yyyy-MM-dd");
134+
formatter.format(new Date());
135+
}
136+
137+
def template(
138+
firstTag: String,
139+
lastTag: String,
140+
mergedPrs: List[String],
141+
commits: Int,
142+
contributors: List[String]
143+
): String =
144+
s"""
145+
|## testcontainers-scala $lastTag
146+
|
147+
|We're happy to announce the release of test-containers $lastTag, which
148+
|
149+
|<table>
150+
|<tbody>
151+
| <tr>
152+
| <td>Commits since last release</td>
153+
| <td align="center">$commits</td>
154+
| </tr>
155+
| <tr>
156+
| <td>Merged PRs</td>
157+
| <td align="center">${mergedPrs.size}</td>
158+
| </tr>
159+
| <tr>
160+
| <td>Contributors</td>
161+
| <td align="center">${contributors.size}</td>
162+
| </tr>
163+
|</tbody>
164+
|</table>
165+
|
166+
|## Contributors
167+
|
168+
|Big thanks to everybody who contributed to this release or reported an issue!
169+
|
170+
|```
171+
|$$ git shortlog -sn --no-merges $firstTag..$lastTag
172+
|${contributors.mkString("\n")}
173+
|```
174+
|
175+
|## Merged PRs
176+
|
177+
|## [$lastTag](https://github.com/$Organization/$Repo/tree/$lastTag) (${today})
178+
|
179+
|[Full Changelog](https://github.com/$Organization/$Repo/compare/$firstTag...$lastTag)
180+
|
181+
|**Merged pull requests:**
182+
|
183+
|${mergedPrs.mkString("\n")}
184+
|""".stripMargin
185+
}
186+
187+
object GenerateRelease
188+
extends CommandApp(
189+
name = "generate-release",
190+
header = "helpers to automate releases",
191+
version = "0.0.0",
192+
main = (ReleaseNotes.opts).map { case args: ReleaseNotes.Arguments =>
193+
ReleaseNotes.run(args)
194+
}
195+
)

0 commit comments

Comments
 (0)