mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-28 19:11:03 +08:00
ci(jetbrains): lock release tag before publish
This commit is contained in:
@@ -49,7 +49,7 @@ jobs:
|
||||
kilo-maintainer-app-id: ${{ secrets.KILO_MAINTAINER_APP_ID }}
|
||||
kilo-maintainer-app-secret: ${{ secrets.KILO_MAINTAINER_APP_SECRET }}
|
||||
|
||||
- name: Create or update release PR
|
||||
- name: Create release tag and PR
|
||||
run: |
|
||||
args=(--kind "$KIND" --version "$VERSION")
|
||||
if [[ -n "$FROM_TAG" ]]; then
|
||||
|
||||
@@ -2,29 +2,61 @@
|
||||
name: publish-jetbrains
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "jetbrains/*"
|
||||
pull_request:
|
||||
types:
|
||||
- closed
|
||||
branches:
|
||||
- main
|
||||
|
||||
concurrency:
|
||||
group: publish-jetbrains-${{ github.ref }}
|
||||
group: publish-jetbrains-pr-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: read
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
if: github.repository == 'Kilo-Org/kilocode'
|
||||
if: >-
|
||||
github.repository == 'Kilo-Org/kilocode' &&
|
||||
github.event.pull_request.merged == true &&
|
||||
startsWith(github.event.pull_request.head.ref, 'jetbrains/release/') &&
|
||||
contains(github.event.pull_request.labels.*.name, 'jetbrains-release') &&
|
||||
github.event.pull_request.head.repo.full_name == github.repository
|
||||
runs-on: blacksmith-8vcpu-ubuntu-2404
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Checkout merged release PR
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.event.pull_request.merge_commit_sha }}
|
||||
|
||||
- name: Setup Bun for validation
|
||||
uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Validate release PR and tag
|
||||
id: release
|
||||
run: bun script/jetbrains-release-validate.ts --pr "$PR_NUMBER"
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
|
||||
- name: Save reviewed changelog
|
||||
run: cp packages/kilo-jetbrains/CHANGELOG.md "$RUNNER_TEMP/jetbrains-CHANGELOG.md"
|
||||
|
||||
- name: Checkout release tag
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ steps.release.outputs.tag }}
|
||||
|
||||
- name: Restore reviewed changelog
|
||||
run: cp "$RUNNER_TEMP/jetbrains-CHANGELOG.md" packages/kilo-jetbrains/CHANGELOG.md
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
@@ -48,39 +80,6 @@ jobs:
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y patchelf zip
|
||||
|
||||
- name: Validate version tag
|
||||
id: version
|
||||
run: |
|
||||
tag="$GITHUB_REF_NAME"
|
||||
if [[ "$tag" != jetbrains/v* ]]; then
|
||||
echo "Unsupported tag '$tag'. Expected jetbrains/v<version>." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
version="${tag#jetbrains/v}"
|
||||
if [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$ ]]; then
|
||||
{
|
||||
echo "version=$version"
|
||||
echo "kind=rc"
|
||||
echo "marketplace_channel=eap"
|
||||
echo "cli_channel=rc"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
{
|
||||
echo "version=$version"
|
||||
echo "kind=stable"
|
||||
echo "marketplace_channel=default"
|
||||
echo "cli_channel=latest"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Unsupported JetBrains plugin version '$version'. Expected jetbrains/vx.y.z-rc.n or jetbrains/vx.y.z." >&2
|
||||
exit 1
|
||||
|
||||
- name: Validate publishing secrets
|
||||
run: |
|
||||
missing=0
|
||||
@@ -101,8 +100,8 @@ jobs:
|
||||
working-directory: packages/kilo-jetbrains
|
||||
run: bun script/build.ts --production --prepare-cli
|
||||
env:
|
||||
KILO_VERSION: ${{ steps.version.outputs.version }}
|
||||
KILO_CHANNEL: ${{ steps.version.outputs.cli_channel }}
|
||||
KILO_VERSION: ${{ steps.release.outputs.version }}
|
||||
KILO_CHANNEL: ${{ steps.release.outputs.cli_channel }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
|
||||
@@ -110,24 +109,24 @@ jobs:
|
||||
working-directory: packages/kilo-jetbrains
|
||||
run: ./gradlew verifyPlugin -Pproduction=true -Pkilo.channel="$CHANNEL"
|
||||
env:
|
||||
CHANNEL: ${{ steps.version.outputs.marketplace_channel }}
|
||||
CHANNEL: ${{ steps.release.outputs.marketplace_channel }}
|
||||
|
||||
- name: Render release notes
|
||||
working-directory: packages/kilo-jetbrains
|
||||
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
|
||||
echo "Missing packages/kilo-jetbrains/CHANGELOG.md entry for $VERSION. Review and merge a release PR before publishing." >&2
|
||||
exit 1
|
||||
fi
|
||||
./gradlew getChangelog --project-version "$VERSION" --no-header --no-empty-sections --output-file=build/release-notes.md
|
||||
env:
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
VERSION: ${{ steps.release.outputs.version }}
|
||||
|
||||
- name: Publish to JetBrains Marketplace
|
||||
working-directory: packages/kilo-jetbrains
|
||||
run: ./gradlew publishPlugin -Pproduction=true -Pkilo.channel="$CHANNEL"
|
||||
env:
|
||||
CHANNEL: ${{ steps.version.outputs.marketplace_channel }}
|
||||
CHANNEL: ${{ steps.release.outputs.marketplace_channel }}
|
||||
JETBRAINS_MARKETPLACE_TOKEN: ${{ secrets.JETBRAINS_MARKETPLACE_TOKEN }}
|
||||
JETBRAINS_CERTIFICATE_CHAIN: ${{ secrets.JETBRAINS_CERTIFICATE_CHAIN }}
|
||||
JETBRAINS_PRIVATE_KEY: ${{ secrets.JETBRAINS_PRIVATE_KEY }}
|
||||
@@ -157,7 +156,7 @@ jobs:
|
||||
|
||||
- name: Upload to GitHub Release
|
||||
run: |
|
||||
tag="$GITHUB_REF_NAME"
|
||||
tag="$TAG"
|
||||
title="JetBrains $VERSION"
|
||||
flags=(--title "$title" --notes-file "$NOTES")
|
||||
if [[ "$KIND" == "rc" ]]; then
|
||||
@@ -171,8 +170,9 @@ jobs:
|
||||
gh release create "$tag" "$ARCHIVE" "${flags[@]}" --repo "$GITHUB_REPOSITORY"
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
KIND: ${{ steps.version.outputs.kind }}
|
||||
TAG: ${{ steps.release.outputs.tag }}
|
||||
VERSION: ${{ steps.release.outputs.version }}
|
||||
KIND: ${{ steps.release.outputs.kind }}
|
||||
ARCHIVE: ${{ steps.archive.outputs.path }}
|
||||
NOTES: packages/kilo-jetbrains/build/release-notes.md
|
||||
|
||||
@@ -180,6 +180,6 @@ jobs:
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: kilo-jetbrains-${{ steps.version.outputs.version }}
|
||||
name: kilo-jetbrains-${{ steps.release.outputs.version }}
|
||||
path: packages/kilo-jetbrains/build/distributions/*.zip
|
||||
if-no-files-found: ignore
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
# kilocode_change - new file
|
||||
name: tag-jetbrains-release
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- closed
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
tag:
|
||||
if: >-
|
||||
github.repository == 'Kilo-Org/kilocode' &&
|
||||
github.event.pull_request.merged == true &&
|
||||
startsWith(github.event.pull_request.head.ref, 'jetbrains/release/') &&
|
||||
contains(github.event.pull_request.labels.*.name, 'jetbrains-release') &&
|
||||
github.event.pull_request.head.repo.full_name == github.repository
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.event.pull_request.merge_commit_sha }}
|
||||
|
||||
- name: Setup Bun
|
||||
uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Setup Git Committer
|
||||
id: committer
|
||||
uses: ./.github/actions/setup-git-committer
|
||||
with:
|
||||
kilo-maintainer-app-id: ${{ secrets.KILO_MAINTAINER_APP_ID }}
|
||||
kilo-maintainer-app-secret: ${{ secrets.KILO_MAINTAINER_APP_SECRET }}
|
||||
|
||||
- name: Create release tag
|
||||
run: bun script/jetbrains-release-tag.ts --pr "$PR_NUMBER"
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.committer.outputs.token }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
@@ -13,8 +13,8 @@
|
||||
|
||||
- Create a JetBrains Marketplace permanent token from Marketplace `My Tokens`.
|
||||
- Add `JETBRAINS_MARKETPLACE_TOKEN` to GitHub Actions secrets or the protected environment.
|
||||
- Confirm `GITHUB_TOKEN` has `contents: write` permission for creating and updating GitHub Releases from `jetbrains/v*` tags.
|
||||
- Confirm `KILO_MAINTAINER_APP_ID` and `KILO_MAINTAINER_APP_SECRET` are available to create release PRs and tags.
|
||||
- Confirm `GITHUB_TOKEN` has `contents: write` permission for creating and updating GitHub Releases for `jetbrains/v*` tags.
|
||||
- Confirm `KILO_MAINTAINER_APP_ID` and `KILO_MAINTAINER_APP_SECRET` are available to create release PRs and immediate release tags.
|
||||
- Optionally create a protected `jetbrains-marketplace` GitHub Environment with required reviewers.
|
||||
- If using an environment, move the Marketplace and signing secrets there and set the workflow job environment.
|
||||
|
||||
@@ -31,8 +31,9 @@
|
||||
|
||||
- Choose an RC version in the form `x.y.z-rc.n`.
|
||||
- Run the `prepare-jetbrains-release` workflow with `kind=rc` and version `x.y.z-rc.n`.
|
||||
- Confirm the workflow created `jetbrains/vx.y.z-rc.n` immediately at the intended source commit.
|
||||
- Review and edit `packages/kilo-jetbrains/CHANGELOG.md` in the generated release PR.
|
||||
- Merge the release PR to create tag `jetbrains/vx.y.z-rc.n`, for example `jetbrains/v7.0.1-rc.1`.
|
||||
- Merge the release PR to trigger publish from `jetbrains/vx.y.z-rc.n`, for example `jetbrains/v7.0.1-rc.1`.
|
||||
- Watch the `publish-jetbrains` workflow.
|
||||
- Download and retain the workflow artifact if needed.
|
||||
- Confirm the update appears on the JetBrains Marketplace `eap` channel.
|
||||
@@ -43,8 +44,9 @@
|
||||
|
||||
- Choose a stable version in the form `x.y.z`.
|
||||
- Run the `prepare-jetbrains-release` workflow with `kind=stable` and version `x.y.z`.
|
||||
- Confirm the workflow created `jetbrains/vx.y.z` immediately at the intended source commit.
|
||||
- Review and edit `packages/kilo-jetbrains/CHANGELOG.md` in the generated release PR.
|
||||
- Merge the release PR to create tag `jetbrains/vx.y.z`.
|
||||
- Merge the release PR to trigger publish from `jetbrains/vx.y.z`.
|
||||
- Watch the `publish-jetbrains` workflow.
|
||||
- Confirm the update appears on the default JetBrains Marketplace channel.
|
||||
- Confirm the GitHub Release for the `jetbrains/vx.y.z` tag exists and contains the JetBrains plugin ZIP asset.
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
# Releasing the JetBrains Plugin
|
||||
|
||||
JetBrains releases use a release PR. The PR is where maintainers review and edit the version and changelog before anything is published.
|
||||
JetBrains releases are locked by an immediate `jetbrains/v<version>` tag, then gated by a reviewed release PR. The tag fixes the exact source code that will be published; the PR is where maintainers review and edit the version and changelog before publishing starts.
|
||||
|
||||
## Create a Release PR
|
||||
The published code comes from `jetbrains/v<version>`. Marketplace and GitHub release notes come from the reviewed changelog merged in the release PR.
|
||||
|
||||
## Create Release Tag And PR
|
||||
|
||||
1. Open the GitHub Actions workflow:
|
||||
|
||||
[https://github.com/Kilo-Org/kilocode/actions/workflows/prepare-jetbrains-release.yml](https://github.com/Kilo-Org/kilocode/actions/workflows/prepare-jetbrains-release.yml)
|
||||
|
||||
2. Click **Run workflow**.
|
||||
2. Click **Run workflow**. This immediately creates `jetbrains/v<version>` at the current `origin/main` commit, then creates or updates the release PR.
|
||||
|
||||
3. Fill the inputs:
|
||||
|
||||
@@ -32,7 +34,7 @@ version=7.3.13
|
||||
|
||||
## Changelog Range Defaults
|
||||
|
||||
The workflow chooses a changelog base automatically:
|
||||
The workflow chooses a changelog base automatically and generates notes against the locked release commit:
|
||||
|
||||
| Release | Default `from_tag` |
|
||||
|---|---|
|
||||
@@ -40,7 +42,7 @@ The workflow chooses a changelog base automatically:
|
||||
| Later RC, e.g. `7.3.13-rc.2` | Previous RC for the same base version. |
|
||||
| Stable, e.g. `7.3.13` | Latest stable JetBrains tag, ignoring RCs. |
|
||||
|
||||
Use `from_tag` only to override this comparison range.
|
||||
Use `from_tag` only to override this comparison range. It does not change the release target commit.
|
||||
|
||||
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`.
|
||||
|
||||
@@ -61,15 +63,17 @@ The PR updates:
|
||||
|
||||
Review and edit `packages/kilo-jetbrains/CHANGELOG.md` before merging. This changelog entry is rendered into JetBrains `<change-notes>`, so it appears on the Marketplace and inside IntelliJ plugin UI.
|
||||
|
||||
The PR can change release metadata such as `packages/kilo-jetbrains/package.json` and `packages/kilo-jetbrains/CHANGELOG.md`, but it does not change the tagged source code that will be built.
|
||||
|
||||
## Merge and Publish
|
||||
|
||||
When the release PR is merged, the `tag-jetbrains-release` workflow validates it and creates:
|
||||
When the release PR is merged, the `publish-jetbrains` workflow validates the existing tag and release PR markers:
|
||||
|
||||
```text
|
||||
jetbrains/v<version>
|
||||
```
|
||||
|
||||
That tag triggers the `publish-jetbrains` workflow:
|
||||
Then it publishes from that tag:
|
||||
|
||||
[https://github.com/Kilo-Org/kilocode/actions/workflows/publish-jetbrains.yml](https://github.com/Kilo-Org/kilocode/actions/workflows/publish-jetbrains.yml)
|
||||
|
||||
@@ -80,7 +84,7 @@ Publishing behavior:
|
||||
| `x.y.z-rc.n` | `eap` | Prerelease |
|
||||
| `x.y.z` | default | Stable release |
|
||||
|
||||
The workflow verifies, signs, and publishes the plugin ZIP, then uploads the ZIP to the matching GitHub Release.
|
||||
The workflow checks out `jetbrains/v<version>` for verification, signing, and Marketplace publishing. It overlays the reviewed `packages/kilo-jetbrains/CHANGELOG.md` from the merged PR before rendering release notes and before `publishPlugin`, so Marketplace metadata and the GitHub Release use the reviewed changelog.
|
||||
|
||||
## Installing RC Builds
|
||||
|
||||
@@ -98,22 +102,28 @@ https://plugins.jetbrains.com/plugins/list?channel=eap&pluginId=28350
|
||||
|
||||
## Manual Recovery
|
||||
|
||||
If the PR was merged but the tag workflow failed after validation, create the tag manually at the merge commit:
|
||||
If the prepare workflow created the tag but failed before creating or updating the PR, rerun the workflow for the same version. It reuses the tag if it still points to the same locked commit.
|
||||
|
||||
If publish validation says the tag points to the wrong SHA, stop and inspect manually. Do not move, delete, or recreate release tags casually.
|
||||
|
||||
If publish failed after merge, rerun the failed `publish-jetbrains` workflow if the failure happened before Marketplace accepted the version. Marketplace may reject a duplicate version after a successful publish.
|
||||
|
||||
If Marketplace publishing succeeded but GitHub Release upload failed, manually create or edit the GitHub Release for the existing tag. Use the reviewed release notes from `packages/kilo-jetbrains/CHANGELOG.md` in the merged release PR.
|
||||
|
||||
If the immediate tag must be created manually because the prepare workflow could not push it, create it at the intended locked `origin/main` commit before merging the release PR:
|
||||
|
||||
```bash
|
||||
git fetch origin main
|
||||
git tag jetbrains/v7.3.13 <merge-sha>
|
||||
git tag jetbrains/v7.3.13 <locked-main-sha>
|
||||
git push origin jetbrains/v7.3.13
|
||||
```
|
||||
|
||||
Do not create a tag before the release PR is merged unless intentionally bypassing the release-PR flow.
|
||||
|
||||
## Required GitHub Actions Secrets
|
||||
|
||||
| Secret | Purpose |
|
||||
|---|---|
|
||||
| `KILO_MAINTAINER_APP_ID` | GitHub App ID used to create/update release PRs and tags. |
|
||||
| `KILO_MAINTAINER_APP_SECRET` | GitHub App private key used to create/update release PRs and tags. |
|
||||
| `KILO_MAINTAINER_APP_ID` | GitHub App ID used to create/update release PRs and immediate release tags. |
|
||||
| `KILO_MAINTAINER_APP_SECRET` | GitHub App private key used to create/update release PRs and immediate release tags. |
|
||||
| `JETBRAINS_MARKETPLACE_TOKEN` | Marketplace API token for publishing. |
|
||||
| `JETBRAINS_CERTIFICATE_CHAIN` | PEM certificate chain for plugin signing. |
|
||||
| `JETBRAINS_PRIVATE_KEY` | PEM private key for plugin signing. |
|
||||
|
||||
@@ -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