74 lines
2.8 KiB
TypeScript
74 lines
2.8 KiB
TypeScript
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)
|
|
}
|