mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-21 05:52:35 +08:00
ci(jetbrains): lock release tag before publish
This commit is contained in:
@@ -48,7 +48,6 @@ const active = new Set([
|
||||
"publish.yml",
|
||||
"smoke-test.yml",
|
||||
"source-check-links.yml",
|
||||
"tag-jetbrains-release.yml",
|
||||
"test-vscode.yml",
|
||||
"test.yml",
|
||||
"triage.yml",
|
||||
|
||||
@@ -45,24 +45,28 @@ await $`git fetch origin main --tags`
|
||||
|
||||
const tag = `jetbrains/v${ver}`
|
||||
const branch = `jetbrains/release/v${ver}`
|
||||
const sha = (await $`git rev-parse origin/main`.text()).trim()
|
||||
const from = values["from-tag"] ?? (await base(ver, kind))
|
||||
const notes = await release(from, tag)
|
||||
const state = await lock(tag, sha, dry)
|
||||
const notes = await release(from, tag, sha)
|
||||
const entry = section(ver, notes)
|
||||
|
||||
console.log(`JetBrains ${kind} release PR`)
|
||||
console.log(`version: ${ver}`)
|
||||
console.log(`base: ${from}`)
|
||||
console.log(`tag: ${tag}`)
|
||||
console.log(`commit: ${sha}`)
|
||||
console.log(`branch: ${branch}`)
|
||||
console.log(`tag state: ${state}`)
|
||||
|
||||
if (dry) {
|
||||
console.log("\nGenerated changelog entry:\n")
|
||||
console.log(entry)
|
||||
console.log("\nDry run complete. No branch, commit, push, or PR was created.")
|
||||
console.log("\nDry run complete. No tag, branch, commit, push, or PR was created.")
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
await $`git checkout -B ${branch} origin/main`
|
||||
await $`git checkout -B ${branch} ${sha}`
|
||||
await writepkg(ver)
|
||||
await writelog(ver, entry)
|
||||
await $`git add packages/kilo-jetbrains/package.json packages/kilo-jetbrains/CHANGELOG.md`
|
||||
@@ -72,7 +76,7 @@ if (changed.exitCode !== 0) await $`git commit -m ${`release(jetbrains): v${ver}
|
||||
|
||||
await $`git push --force-with-lease origin ${branch}`
|
||||
|
||||
const text = body(ver, kind, from, tag, notes)
|
||||
const text = body(ver, kind, from, tag, sha, notes)
|
||||
const view = await $`gh pr view ${branch} --repo ${repo} --json number --jq .number`.nothrow()
|
||||
if (view.exitCode === 0 && view.stdout.toString().trim()) {
|
||||
const num = view.stdout.toString().trim()
|
||||
@@ -120,13 +124,29 @@ async function base(ver: string, kind: "rc" | "stable") {
|
||||
return hit.tag
|
||||
}
|
||||
|
||||
async function release(from: string, tag: string) {
|
||||
const res = await $`gh api repos/${repo}/releases/generate-notes --method POST -f tag_name=${tag} -f target_commitish=main -f previous_tag_name=${from} --jq .body`
|
||||
async function lock(tag: string, sha: string, dry: boolean) {
|
||||
const res = await $`git rev-parse -q --verify ${`refs/tags/${tag}`}`.nothrow()
|
||||
if (res.exitCode === 0) {
|
||||
const got = (await $`git rev-list -n 1 ${tag}`.text()).trim()
|
||||
if (got === sha) return "exists"
|
||||
throw new Error(`${tag} already exists at ${got}, expected ${sha}`)
|
||||
}
|
||||
if (dry) return "would-create"
|
||||
await $`git tag ${tag} ${sha}`
|
||||
await $`git push origin ${tag}`
|
||||
return "created"
|
||||
}
|
||||
|
||||
async function release(from: string, tag: string, sha: string) {
|
||||
const res = await $`gh api repos/${repo}/releases/generate-notes --method POST -f tag_name=${tag} -f target_commitish=${sha} -f previous_tag_name=${from} --jq .body`
|
||||
.quiet()
|
||||
.nothrow()
|
||||
if (res.exitCode === 0) return res.stdout.toString().trim()
|
||||
|
||||
const text = await $`git log --format=%s ${from}..origin/main`.text()
|
||||
const base = await $`git rev-parse -q --verify ${from}`.nothrow()
|
||||
if (base.exitCode !== 0) throw new Error(`Previous JetBrains tag not found: ${from}`)
|
||||
|
||||
const text = await $`git log --format=%s ${from}..${sha}`.text()
|
||||
const lines = text
|
||||
.split(/\r?\n/)
|
||||
.map((item) => item.trim())
|
||||
@@ -197,7 +217,7 @@ function regex(ver: string) {
|
||||
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, sha: string, notes: string) {
|
||||
return `## Summary
|
||||
- Prepare JetBrains ${kind} release ${ver}.
|
||||
- Review and edit \`packages/kilo-jetbrains/CHANGELOG.md\` before merging.
|
||||
@@ -206,6 +226,7 @@ JetBrains-Version: ${ver}
|
||||
JetBrains-Kind: ${kind}
|
||||
JetBrains-From-Tag: ${from}
|
||||
JetBrains-Tag: ${tag}
|
||||
JetBrains-Commit: ${sha}
|
||||
|
||||
## Generated Notes
|
||||
${notes || "No notable changes."}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// kilocode_change - new file
|
||||
|
||||
import { $ } from "bun"
|
||||
import { appendFileSync } from "node:fs"
|
||||
import semver from "semver"
|
||||
import { parseArgs } from "util"
|
||||
|
||||
@@ -17,7 +18,10 @@ const { values } = parseArgs({
|
||||
|
||||
if (values.help) {
|
||||
console.log(`
|
||||
Usage: bun script/jetbrains-release-tag.ts --pr <number> [--dry]
|
||||
Usage: bun script/jetbrains-release-validate.ts --pr <number> [--dry]
|
||||
|
||||
Validates a merged JetBrains release PR and the pre-created immutable release tag.
|
||||
This helper never creates, moves, deletes, or pushes tags.
|
||||
`)
|
||||
process.exit(0)
|
||||
}
|
||||
@@ -40,11 +44,25 @@ if (!data.headRefName.startsWith("jetbrains/release/")) throw new Error("PR head
|
||||
if (data.isCrossRepository) throw new Error("JetBrains release PR must come from this repository")
|
||||
if (!data.mergeCommit?.oid) throw new Error("PR has no merge commit")
|
||||
|
||||
const ver = marker(data.body, "JetBrains-Version") ?? data.headRefName.replace(/^jetbrains\/release\/v/, "")
|
||||
const tag = marker(data.body, "JetBrains-Tag") ?? `jetbrains/v${ver}`
|
||||
const ver = need(data.body, "JetBrains-Version")
|
||||
const kind = need(data.body, "JetBrains-Kind")
|
||||
const tag = need(data.body, "JetBrains-Tag")
|
||||
const commit = need(data.body, "JetBrains-Commit")
|
||||
|
||||
if (!semver.valid(ver)) throw new Error(`Invalid JetBrains version: ${ver}`)
|
||||
if (kind !== "rc" && kind !== "stable") throw new Error(`Invalid JetBrains kind: ${kind}`)
|
||||
if (kind === "rc" && !/^\d+\.\d+\.\d+-rc\.\d+$/.test(ver)) throw new Error("RC versions must match x.y.z-rc.n")
|
||||
if (kind === "stable" && !/^\d+\.\d+\.\d+$/.test(ver)) throw new Error("Stable versions must match x.y.z")
|
||||
if (tag !== `jetbrains/v${ver}`) throw new Error(`Tag ${tag} does not match version ${ver}`)
|
||||
if (!/^jetbrains\/v\d+\.\d+\.\d+(-rc\.\d+)?$/.test(tag)) throw new Error(`Invalid JetBrains tag: ${tag}`)
|
||||
if (!/^[0-9a-f]{40}$/i.test(commit)) throw new Error(`Invalid JetBrains commit: ${commit}`)
|
||||
|
||||
await $`git fetch origin --tags`
|
||||
const existing = await $`git rev-parse -q --verify ${`refs/tags/${tag}`}`.nothrow()
|
||||
if (existing.exitCode !== 0) throw new Error(`${tag} does not exist`)
|
||||
|
||||
const sha = (await $`git rev-list -n 1 ${tag}`.text()).trim()
|
||||
if (sha !== commit) throw new Error(`${tag} points at ${sha}, expected ${commit}`)
|
||||
|
||||
const pkg = await Bun.file("packages/kilo-jetbrains/package.json").json()
|
||||
if (pkg.version !== ver) throw new Error(`packages/kilo-jetbrains/package.json version is ${pkg.version}, expected ${ver}`)
|
||||
@@ -52,27 +70,30 @@ if (pkg.version !== ver) throw new Error(`packages/kilo-jetbrains/package.json v
|
||||
const changelog = await Bun.file("packages/kilo-jetbrains/CHANGELOG.md").text()
|
||||
if (!changelog.includes(`## [${ver}]`)) throw new Error(`CHANGELOG.md is missing section for ${ver}`)
|
||||
|
||||
await $`git fetch origin --tags`
|
||||
const existing = await $`git rev-parse -q --verify ${`refs/tags/${tag}`}`.nothrow()
|
||||
if (existing.exitCode === 0) {
|
||||
const sha = (await $`git rev-list -n 1 ${tag}`.text()).trim()
|
||||
if (sha === data.mergeCommit.oid) {
|
||||
console.log(`${tag} already exists at ${sha}`)
|
||||
process.exit(0)
|
||||
}
|
||||
throw new Error(`${tag} already exists at ${sha}, expected ${data.mergeCommit.oid}`)
|
||||
const marketplace = kind === "rc" ? "eap" : "default"
|
||||
const cli = kind === "rc" ? "rc" : "latest"
|
||||
const output = {
|
||||
version: ver,
|
||||
kind,
|
||||
tag,
|
||||
commit,
|
||||
merge: data.mergeCommit.oid,
|
||||
marketplace_channel: marketplace,
|
||||
cli_channel: cli,
|
||||
}
|
||||
|
||||
console.log(`Creating ${tag} at ${data.mergeCommit.oid}`)
|
||||
if (values.dry) {
|
||||
console.log("Dry run complete. No tag was created.")
|
||||
process.exit(0)
|
||||
for (const [key, value] of Object.entries(output)) console.log(`${key}=${value}`)
|
||||
if (process.env.GITHUB_OUTPUT && !values.dry) {
|
||||
appendFileSync(process.env.GITHUB_OUTPUT, Object.entries(output).map(([key, value]) => `${key}=${value}\n`).join(""))
|
||||
}
|
||||
|
||||
await $`git tag ${tag} ${data.mergeCommit.oid}`
|
||||
await $`git push origin ${tag}`
|
||||
|
||||
function marker(body: string, key: string) {
|
||||
const line = body.split(/\r?\n/).find((item) => item.startsWith(`${key}:`))
|
||||
return line?.slice(key.length + 1).trim()
|
||||
}
|
||||
|
||||
function need(body: string, key: string) {
|
||||
const value = marker(body, key)
|
||||
if (!value) throw new Error(`PR body is missing ${key}`)
|
||||
return value
|
||||
}
|
||||
Reference in New Issue
Block a user