mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-29 03:44:06 +08:00
Merge pull request #11246 from Kilo-Org/sprout-principal
fix: include prerelease notes in stable releases
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
import { expect, test } from "bun:test"
|
||||
import { buildNotes } from "./release-notes"
|
||||
|
||||
const section = (version: string, body: string) => `## ${version}\n\n${body}\n`
|
||||
const release = (tagName: string, isPrerelease = false, isDraft = false) => ({ tagName, isPrerelease, isDraft })
|
||||
|
||||
test("prerelease notes contain only their own section", () => {
|
||||
const changelog = section("1.2.3", "current prerelease") + section("1.2.2", "previous prerelease")
|
||||
const notes = buildNotes({
|
||||
version: "1.2.3",
|
||||
prerelease: true,
|
||||
releases: [release("v1.2.2", true)],
|
||||
changelog,
|
||||
})
|
||||
|
||||
expect(notes).toBe("current prerelease")
|
||||
})
|
||||
|
||||
test("stable notes contain only published prereleases since the previous stable", () => {
|
||||
const changelog = [
|
||||
section("1.2.6", "final"),
|
||||
section("1.2.5", "published prerelease"),
|
||||
section("1.2.4", "draft prerelease"),
|
||||
section("1.2.3", "unpublished section"),
|
||||
section("1.2.1", "previous stable"),
|
||||
section("1.2.0", "older prerelease"),
|
||||
].join("\n")
|
||||
const notes = buildNotes({
|
||||
version: "1.2.6",
|
||||
prerelease: false,
|
||||
releases: [release("v1.2.5", true), release("v1.2.4", true, true), release("v1.2.1"), release("v1.2.0", true)],
|
||||
changelog,
|
||||
})
|
||||
|
||||
expect(notes).toBe("## 1.2.6\n\nfinal\n\n## 1.2.5\n\npublished prerelease")
|
||||
})
|
||||
|
||||
test("stable notes include prereleases when the current section is empty", () => {
|
||||
const notes = buildNotes({
|
||||
version: "1.2.3",
|
||||
prerelease: false,
|
||||
releases: [release("v1.2.2", true), release("v1.2.1")],
|
||||
changelog: section("1.2.3", "") + section("1.2.2", "prerelease") + section("1.2.1", "stable"),
|
||||
})
|
||||
|
||||
expect(notes).toBe("## 1.2.2\n\nprerelease")
|
||||
})
|
||||
|
||||
test("missing and empty prerelease sections use the fallback", () => {
|
||||
expect(buildNotes({ version: "2.0.0", prerelease: true, releases: [], changelog: "" })).toBe("No notable changes")
|
||||
expect(buildNotes({ version: "2.0.0", prerelease: true, releases: [], changelog: section("2.0.0", "") })).toBe(
|
||||
"No notable changes",
|
||||
)
|
||||
expect(buildNotes({ version: "2.0.0", prerelease: false, releases: [release("v1.9.0", true)], changelog: "" })).toBe(
|
||||
"No notable changes",
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1,73 @@
|
||||
import { $ } from "bun"
|
||||
|
||||
const fallback = "No notable changes"
|
||||
const pattern = /^v?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)$/
|
||||
|
||||
type Release = {
|
||||
tagName: string
|
||||
isDraft: boolean
|
||||
isPrerelease: boolean
|
||||
}
|
||||
|
||||
export function buildNotes(input: { version: string; prerelease: boolean; releases: Release[]; changelog: string }) {
|
||||
const target = version(input.version)
|
||||
if (!target) throw new Error(`Invalid release version: ${input.version}`)
|
||||
|
||||
const sections = parse(input.changelog)
|
||||
const current = sections.find((section) => section.version === target)
|
||||
if (!current) return fallback
|
||||
if (input.prerelease) return current.body || fallback
|
||||
|
||||
const stable = input.releases
|
||||
.flatMap((release) => {
|
||||
const value = version(release.tagName)
|
||||
if (!value || release.isDraft || release.isPrerelease || compare(value, target) >= 0) return []
|
||||
return [value]
|
||||
})
|
||||
.sort((a, b) => compare(b, a))[0]
|
||||
const versions = new Set([
|
||||
target,
|
||||
...input.releases.flatMap((release) => {
|
||||
const value = version(release.tagName)
|
||||
if (!value || release.isDraft || !release.isPrerelease || compare(value, target) >= 0) return []
|
||||
if (stable && compare(value, stable) <= 0) return []
|
||||
return [value]
|
||||
}),
|
||||
])
|
||||
const notes = sections
|
||||
.filter((section) => versions.has(section.version) && section.body)
|
||||
.map((section) => `## ${section.version}\n\n${section.body}`)
|
||||
return notes.join("\n\n") || fallback
|
||||
}
|
||||
|
||||
export async function publishNotes(input: { version: string; prerelease: boolean; repo?: string; temp?: string }) {
|
||||
const repo = input.repo ? ["--repo", input.repo] : []
|
||||
const releases: Release[] = await $`gh release list --limit 1000 --json tagName,isDraft,isPrerelease ${repo}`.json()
|
||||
const changelog = await Bun.file(new URL("../../packages/kilo-vscode/CHANGELOG.md", import.meta.url)).text()
|
||||
const body = buildNotes({ ...input, releases, changelog })
|
||||
const notes = `${input.temp ?? "/tmp"}/release-notes.txt`
|
||||
const target = input.version.startsWith("v") ? input.version : `v${input.version}`
|
||||
const flags = input.prerelease ? ["--draft=false", "--prerelease"] : ["--draft=false"]
|
||||
await Bun.write(notes, body)
|
||||
flags.push("--title", `${target} (${input.prerelease ? "pre-release" : "release"})`, "--notes-file", notes)
|
||||
await $`gh release edit ${target} ${flags} ${repo}`
|
||||
}
|
||||
|
||||
function parse(input: string) {
|
||||
const matches = Array.from(input.matchAll(/^##\s+(.+)$/gm))
|
||||
return matches.flatMap((match, index) => {
|
||||
const value = version(match[1] ?? "")
|
||||
if (!value) return []
|
||||
const start = (match.index ?? 0) + match[0].length
|
||||
const end = matches[index + 1]?.index ?? input.length
|
||||
return [{ version: value, body: input.slice(start, end).trim() }]
|
||||
})
|
||||
}
|
||||
|
||||
function version(input: string) {
|
||||
return input.trim().match(pattern)?.[1]
|
||||
}
|
||||
|
||||
function compare(a: string, b: string) {
|
||||
return Bun.semver.order(a, b)
|
||||
}
|
||||
+8
-35
@@ -88,27 +88,14 @@ if (Script.release) {
|
||||
}
|
||||
// kilocode_change end
|
||||
|
||||
// kilocode_change start
|
||||
// kilocode_change end
|
||||
|
||||
// kilocode_change start - mark prerelease GitHub releases accordingly
|
||||
// and populate release notes from the changelog updated by changeset above.
|
||||
// Use an absolute path for the CHANGELOG because the imported SDK build
|
||||
// script chdirs into packages/sdk/js, so a relative path would miss the file
|
||||
// and fall through to the "No notable changes" default.
|
||||
const kind = Script.preview ? "pre-release" : "release"
|
||||
const flags = Script.preview ? ["--draft=false", "--prerelease"] : ["--draft=false"]
|
||||
flags.push("--title", `v${Script.version} (${kind})`)
|
||||
const changelogPath = fileURLToPath(new URL("../packages/kilo-vscode/CHANGELOG.md", import.meta.url))
|
||||
const changelog = await Bun.file(changelogPath)
|
||||
.text()
|
||||
.catch(() => "")
|
||||
const body = extractLatestSection(changelog) || "No notable changes"
|
||||
const tmp = process.env.RUNNER_TEMP ?? "/tmp"
|
||||
const notes = `${tmp}/release-notes.txt`
|
||||
await Bun.write(notes, body)
|
||||
flags.push("--notes-file", notes)
|
||||
await $`gh release edit v${Script.version} ${flags} --repo ${process.env.GH_REPO}`
|
||||
// kilocode_change start - publish channel-aware GitHub release notes
|
||||
const { publishNotes } = await import("./kilocode/release-notes")
|
||||
await publishNotes({
|
||||
version: Script.version,
|
||||
prerelease: Script.preview,
|
||||
repo: process.env.GH_REPO,
|
||||
temp: process.env.RUNNER_TEMP,
|
||||
})
|
||||
// kilocode_change end
|
||||
}
|
||||
|
||||
@@ -135,17 +122,3 @@ await import(`../packages/kilo-vscode/script/publish.ts`)
|
||||
|
||||
const dir = fileURLToPath(new URL("..", import.meta.url))
|
||||
process.chdir(dir)
|
||||
|
||||
// kilocode_change start - extract latest changelog section for release notes
|
||||
function extractLatestSection(changelog: string): string {
|
||||
if (!changelog) return ""
|
||||
const lines = changelog.split("\n")
|
||||
const start = lines.findIndex((line) => /^## /.test(line))
|
||||
if (start < 0) return ""
|
||||
const end = lines.findIndex((line, i) => i > start && /^## /.test(line))
|
||||
return lines
|
||||
.slice(start + 1, end < 0 ? undefined : end)
|
||||
.join("\n")
|
||||
.trim()
|
||||
}
|
||||
// kilocode_change end
|
||||
|
||||
Reference in New Issue
Block a user