fix(jetbrains): address release workflow review

This commit is contained in:
kirillk
2026-05-27 12:17:49 -04:00
parent a411ba9321
commit beb8a406f0
4 changed files with 47 additions and 11 deletions
+6 -1
View File
@@ -114,7 +114,12 @@ jobs:
- name: Render release notes - name: Render release notes
working-directory: packages/kilo-jetbrains working-directory: packages/kilo-jetbrains
run: ./gradlew getChangelog --project-version "$VERSION" --no-header --no-empty-sections --output-file=build/release-notes.md run: |
if ! grep -Fq "## [$VERSION]" CHANGELOG.md; then
echo "Missing packages/kilo-jetbrains/CHANGELOG.md entry for $VERSION. Create a release PR or add the changelog section before tagging." >&2
exit 1
fi
./gradlew getChangelog --project-version "$VERSION" --no-header --no-empty-sections --output-file=build/release-notes.md
env: env:
VERSION: ${{ steps.version.outputs.version }} VERSION: ${{ steps.version.outputs.version }}
@@ -5,6 +5,8 @@ on:
pull_request: pull_request:
types: types:
- closed - closed
branches:
- main
permissions: permissions:
contents: write contents: write
+2
View File
@@ -42,6 +42,8 @@ The workflow chooses a changelog base automatically:
Use `from_tag` only to override this comparison range. Use `from_tag` only to override this comparison range.
For the first stable JetBrains release, there may be no previous stable tag yet. In that case, pass the last RC or another reviewed JetBrains tag as `from_tag`.
## Review the PR ## Review the PR
The workflow creates or updates a branch like: The workflow creates or updates a branch like:
+37 -10
View File
@@ -95,7 +95,7 @@ async function base(ver: string, kind: "rc" | "stable") {
.map((item) => ({ tag: item, ver: item.replace(/^jetbrains\/v/, "") })) .map((item) => ({ tag: item, ver: item.replace(/^jetbrains\/v/, "") }))
.filter((item) => semver.valid(item.ver)) .filter((item) => semver.valid(item.ver))
const target = semver.parse(ver)! const want = semver.parse(ver)!
const stable = tags const stable = tags
.filter((item) => !semver.prerelease(item.ver) && semver.lt(item.ver, ver)) .filter((item) => !semver.prerelease(item.ver) && semver.lt(item.ver, ver))
.sort((a, b) => semver.rcompare(a.ver, b.ver)) .sort((a, b) => semver.rcompare(a.ver, b.ver))
@@ -110,7 +110,7 @@ async function base(ver: string, kind: "rc" | "stable") {
.filter((item) => { .filter((item) => {
const parsed = semver.parse(item.ver) const parsed = semver.parse(item.ver)
if (!parsed) return false if (!parsed) return false
if (parsed.major !== target.major || parsed.minor !== target.minor || parsed.patch !== target.patch) return false if (parsed.major !== want.major || parsed.minor !== want.minor || parsed.patch !== want.patch) return false
return Boolean(semver.prerelease(item.ver)) && semver.lt(item.ver, ver) return Boolean(semver.prerelease(item.ver)) && semver.lt(item.ver, ver)
}) })
.sort((a, b) => semver.rcompare(a.ver, b.ver)) .sort((a, b) => semver.rcompare(a.ver, b.ver))
@@ -137,17 +137,41 @@ async function release(from: string, tag: string) {
function section(ver: string, notes: string) { function section(ver: string, notes: string) {
const date = new Date().toISOString().slice(0, 10) const date = new Date().toISOString().slice(0, 10)
const lines = bullets(notes) const groups = entries(notes)
return [`## [${ver}] - ${date}`, "", "### Changed", ...lines, ""].join("\n") const lines = [`## [${ver}] - ${date}`, ""]
for (const title of ["Added", "Fixed", "Changed"] as const) {
const items = groups.get(title)
if (!items?.length) continue
lines.push(`### ${title}`, ...items, "")
}
if (lines.length === 2) lines.push("### Changed", "- No notable changes.", "")
return lines.join("\n")
} }
function bullets(notes: string) { function entries(notes: string) {
const lines = notes const groups = new Map<string, string[]>([
["Added", []],
["Fixed", []],
["Changed", []],
])
for (const line of notes
.split(/\r?\n/) .split(/\r?\n/)
.map((item) => item.trim()) .map((item) => item.trim())
.filter((item) => item.startsWith("- ") || item.startsWith("* ")) .filter((item) => item.startsWith("- ") || item.startsWith("* "))
.map((item) => `- ${item.slice(2).trim()}`) .map((item) => item.slice(2).trim())) {
return lines.length > 0 ? lines : ["- No notable changes."] if (line.startsWith("@") && line.includes(" made their first contribution ")) continue
const text = `- ${line}`
if (/^(feat|add)(\(.+\))?:/i.test(line)) {
groups.get("Added")!.push(text)
continue
}
if (/^(fix|bug)(\(.+\))?:/i.test(line)) {
groups.get("Fixed")!.push(text)
continue
}
groups.get("Changed")!.push(text)
}
return groups
} }
async function writepkg(ver: string) { async function writepkg(ver: string) {
@@ -157,7 +181,10 @@ async function writepkg(ver: string) {
} }
async function writelog(ver: string, entry: string) { async function writelog(ver: string, entry: string) {
const current = await Bun.file(log).text().catch(() => "# Changelog\n\n## [Unreleased]\n") const current = await Bun.file(log).text().catch((err: NodeJS.ErrnoException) => {
if (err.code === "ENOENT") return "# Changelog\n\n## [Unreleased]\n"
throw err
})
const clean = current.replace(regex(ver), "").replace(/\n{3,}/g, "\n\n") const clean = current.replace(regex(ver), "").replace(/\n{3,}/g, "\n\n")
const marker = "## [Unreleased]" const marker = "## [Unreleased]"
if (!clean.includes(marker)) throw new Error("CHANGELOG.md must contain ## [Unreleased]") if (!clean.includes(marker)) throw new Error("CHANGELOG.md must contain ## [Unreleased]")
@@ -167,7 +194,7 @@ async function writelog(ver: string, entry: string) {
function regex(ver: string) { function regex(ver: string) {
const safe = ver.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") const safe = ver.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
return new RegExp(`\\n?## \\[${safe}\\][\\s\\S]*?(?=\\n## \\[|$)`, "m") return new RegExp(`\\n?## \\[${safe}\\][\\s\\S]*?(?=\\n## \\[|$)`)
} }
function body(ver: string, kind: string, from: string, tag: string, notes: string) { function body(ver: string, kind: string, from: string, tag: string, notes: string) {