mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2026-10-25 15:42:22 +01:00
Our pipeline could mark issues on youtrack as fixed even if they were not in release branch. Added single script for all pipelines to deremine which commits enetered given release so changelog, whats new and release itself are using exactly same logic
268 lines
14 KiB
YAML
268 lines
14 KiB
YAML
name: Prepare What's New with Claude
|
||
|
||
# The PR goes to master so that master keeps every page and the full changelog, but the page itself
|
||
# describes the `release` branch: the release is published from that branch as is (only the EAP build
|
||
# moves it to master), so changes that are only on master are not part of the upcoming release. The
|
||
# TeamCity release build takes the merged whatsnew-tbr.html from master and promotes it.
|
||
|
||
on:
|
||
workflow_dispatch: # Manually triggered by a maintainer
|
||
|
||
jobs:
|
||
prepare-whats-new:
|
||
runs-on: ubuntu-latest
|
||
if: github.repository == 'JetBrains/ideavim'
|
||
|
||
permissions:
|
||
contents: write
|
||
pull-requests: write
|
||
id-token: write
|
||
issues: read
|
||
actions: read
|
||
|
||
steps:
|
||
- name: Checkout repository
|
||
uses: actions/checkout@v4
|
||
with:
|
||
fetch-depth: 0 # Full history so the changelog can be analyzed and the PR branch pushed
|
||
|
||
- name: Fetch the release branch
|
||
id: release
|
||
run: |
|
||
git fetch origin release:refs/remotes/origin/release
|
||
echo "commit=$(git rev-parse origin/release)" >> "$GITHUB_OUTPUT"
|
||
|
||
- name: Set up Node.js
|
||
uses: actions/setup-node@v4
|
||
with:
|
||
node-version: '20'
|
||
|
||
# Claude runs prepareReleaseChangelog.ts to see which changelog entries the release contains
|
||
- name: Install script dependencies
|
||
run: npm ci --no-fund --no-audit
|
||
working-directory: scripts-ts
|
||
|
||
# Tells apart "the JWT expired" from "the quota is gone" BEFORE Claude runs -
|
||
# claude-code-action reports both as a bare `result is_error:true`.
|
||
- name: Check Claude credentials and quota
|
||
id: preflight
|
||
uses: ./.github/actions/claude-preflight
|
||
with:
|
||
base_url: ${{ secrets.PROXY_URL }}
|
||
jwt: ${{ secrets.ANTHROPIC_API_KEY }}
|
||
label: pre-flight
|
||
|
||
- name: Run Claude Code to update the changelog and prepare the What's New page
|
||
id: claude
|
||
uses: anthropics/claude-code-action@v1
|
||
env:
|
||
ANTHROPIC_BASE_URL: ${{ secrets.PROXY_URL }}
|
||
ANTHROPIC_CUSTOM_HEADERS: |
|
||
Grazie-Agent: {"name":"ideavim-claude-code-action","version":"github-actions"}
|
||
Grazie-Authenticate-JWT: ${{ secrets.ANTHROPIC_API_KEY }}
|
||
with:
|
||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||
|
||
prompt: |
|
||
Do TWO things in order, in this single run, then open ONE pull request that
|
||
contains both changes. The What's New page is derived from the changelog, so the
|
||
changelog MUST be brought up to date first.
|
||
|
||
## Step 1 — Update the changelog first
|
||
- Use the `changelog` skill to load the maintenance rules, then follow them.
|
||
- Review commits since the last documented version and add any missing
|
||
user-facing entries to the `## [To Be Released]` section of `CHANGES.md`
|
||
(under `### Features:`, `### Fixes:`, `### Changes:`, `### Merged PRs:`).
|
||
- Respect every skill rule: exclusions (Dependabot/Claude PRs, internal
|
||
refactors, test-only/doc-only changes, the `api` module, and the Vim Everywhere
|
||
/ Hints project), the writing style, and "each change in exactly one subsection".
|
||
- Only EDIT `CHANGES.md` in the working tree here. Do NOT open a separate PR for it.
|
||
|
||
## Step 2 — Create or update the What's New page (whatsnew-tbr.html)
|
||
The page for the upcoming release lives at `src/main/resources/whatsnew-tbr.html`.
|
||
After each release it is promoted to `whatsnew-<version>.html` and the `tbr` file is
|
||
gone, so USUALLY it does NOT exist when this workflow runs — only the version-specific
|
||
pages for older releases (`whatsnew-<version>.html`) remain.
|
||
|
||
### Describe only what the release will contain
|
||
The release is built from the `origin/release` branch, which may be BEHIND master, so not
|
||
everything under `## [To Be Released]` in `CHANGES.md` is part of it. After Step 1, run
|
||
exactly this command (do not work it out from `git log` yourself):
|
||
|
||
scripts-ts/node_modules/.bin/tsx scripts-ts/src/prepareReleaseChangelog.ts --print . origin/release
|
||
|
||
It prints the `## To Be Released` section the release will be published with - the same one
|
||
the release build puts in the plugin. Below, "the release section" means this output. Use
|
||
only its Features and Fixes; you may skip Merged PRs. The entries it leaves out stay in
|
||
`CHANGES.md`; they will be on the page of the next release. Run it again if you change
|
||
`CHANGES.md` afterwards.
|
||
Leave the `<!-- whatsnew-release-commit: ... -->` line at the top of the page as it is
|
||
(or do not add one): this workflow records there which release commit the page describes.
|
||
|
||
First decide what to do by checking the working tree:
|
||
- List `src/main/resources/whatsnew-*.html`.
|
||
- **If `whatsnew-tbr.html` does NOT exist (the normal case):** create it fresh,
|
||
describing every Feature and Fix in the release section.
|
||
- **If `whatsnew-tbr.html` ALREADY exists (it was generated earlier this cycle):**
|
||
do NOT rebuild it from scratch. Diff it against the release section — work out which
|
||
changelog entries are NOT yet represented on the page — and
|
||
update the page to incorporate ONLY those new/changed items, preserving the existing
|
||
sections, ordering and wording already present. This is an incremental update, not a
|
||
rewrite. (If nothing in the changelog is new relative to the page, make no changes
|
||
and do not open a PR.)
|
||
|
||
- If the release section has no Features or Fixes, STOP:
|
||
do not create or change the HTML and do not open a PR. Explain why in your final message.
|
||
|
||
### Style — match the existing pages EXACTLY
|
||
Use the NEWEST version-specific page as the canonical style reference: pick
|
||
`src/main/resources/whatsnew-<highest-version>.html` (fall back to `whatsnew-tbr.html`
|
||
only if no version-specific page exists) and read it first. It is a terminal-window
|
||
"blog post": monospace font, a title bar with traffic-light dots, `❯`-prefixed `<h2>`
|
||
headings, `#`-prefixed `.kicker` labels, a `.grid` of `.mini` cards, syntax-colored
|
||
`<pre>` blocks (`.cm` comments, `.str` strings, `.key` keys), and a footer prompt with links.
|
||
|
||
Copy its `<head>` and entire `<style>` block VERBATIM. You MUST keep, unchanged:
|
||
- the `data-theme="__THEME__"` attribute on `<html>` and the `__VERSION__` token —
|
||
the IDE replaces both at runtime; never remove or rename them,
|
||
- the `:root`, `:root[data-theme="dark"]`, and
|
||
`@media (prefers-color-scheme: dark)` variable blocks,
|
||
- every existing CSS class (`.term`, `.titlebar`, `.kicker`, `.section`, `.mini`,
|
||
`.grid`, `pre .cm/.str/.key`, `.caret`, `footer`, etc.).
|
||
|
||
Only change the human content: the `<title>`, the title-bar `.title`, the intro
|
||
`.runline`/`.out` block, the feature `.section`s, the "polish & fixes" list, and
|
||
the footer. Lead with the most interesting Features as full `.section`s (kicker +
|
||
`<h2>` + paragraph, plus a `<pre>` example when the entry shows commands or
|
||
`set`/`map` syntax); group smaller items into a `.grid` of `.mini` cards. Render
|
||
the Fixes as a final "polish & fixes" `<ul>`, each `<li>` with a short bolded lead.
|
||
Rewrite changelog lines into concise, friendly, user-facing prose — do NOT put
|
||
`VIM-####` ids or raw issue URLs on the page. Escape HTML correctly inside
|
||
`code`/`kbd`/`pre` (`<` `>` `&`).
|
||
|
||
## Do NOT set the version
|
||
- Never hard-code a version anywhere in the page. Leave the `__VERSION__` token
|
||
exactly as-is wherever it appears — the release pipeline (TeamCity) and the IDE
|
||
substitute the real version at build/runtime.
|
||
|
||
## Open ONE pull request
|
||
- Create a branch named `whatsnew-tbr` (reset it if it already exists).
|
||
- Include BOTH the `CHANGES.md` update and the `whatsnew-tbr.html` change.
|
||
- Open it against `master` with:
|
||
- Title: "Prepare What's New for the upcoming release"
|
||
- Body: a short summary of the changelog entries added and the What's New highlights.
|
||
|
||
# Changelog needs the skill + git/gh/web; the page generation needs file read/write.
|
||
claude_args: '--allowed-tools "Skill,Read,Write,Edit,Glob,Grep,Bash(git:*),Bash(gh:*),Bash(scripts-ts/node_modules/.bin/tsx scripts-ts/src/prepareReleaseChangelog.ts:*),WebSearch,WebFetch(domain:plugins.jetbrains.com),WebFetch(domain:youtrack.jetbrains.com),WebFetch(domain:github.com)"'
|
||
|
||
# The release build refuses a page written for another commit of the release branch: an EAP build or a
|
||
# cherry-pick after this run changes what the release contains. So the commit is recorded even when
|
||
# the page itself needed no changes - then in a PR of its own.
|
||
- name: Record the release commit the page describes
|
||
env:
|
||
GH_TOKEN: ${{ github.token }}
|
||
RELEASE_COMMIT: ${{ steps.release.outputs.commit }}
|
||
run: |
|
||
set -e
|
||
tbr="src/main/resources/whatsnew-tbr.html"
|
||
marker="<!-- whatsnew-release-commit: $RELEASE_COMMIT -->"
|
||
git config user.name "IdeaVim Bot"
|
||
git config user.email "maintainers@ideavim.dev"
|
||
git fetch origin master
|
||
pr=$(gh pr list --head whatsnew-tbr --state open --json number --jq '.[0].number // empty')
|
||
if [ -n "$pr" ]; then
|
||
git fetch origin whatsnew-tbr
|
||
git checkout -f -B whatsnew-tbr origin/whatsnew-tbr
|
||
elif git cat-file -e "origin/master:$tbr" 2>/dev/null; then
|
||
git checkout -f -B whatsnew-tbr origin/master
|
||
else
|
||
echo "No What's New page to record the release commit in"
|
||
exit 0
|
||
fi
|
||
if grep -qxF "$marker" "$tbr"; then
|
||
echo "The page already describes release commit $RELEASE_COMMIT"
|
||
exit 0
|
||
fi
|
||
sed -i '/^<!-- whatsnew-release-commit: .* -->$/d' "$tbr"
|
||
sed -i "1i $marker" "$tbr"
|
||
git commit -qam "Record release commit $RELEASE_COMMIT for What's New"
|
||
if [ -n "$pr" ]; then
|
||
git push origin whatsnew-tbr
|
||
echo "Recorded release commit $RELEASE_COMMIT in PR #$pr"
|
||
else
|
||
git push -f origin whatsnew-tbr
|
||
gh pr create --base master --head whatsnew-tbr \
|
||
--title "Prepare What's New for the upcoming release" \
|
||
--body "The release branch moved to $RELEASE_COMMIT, and the What's New page needed no changes for it. This only records the new commit, so the release build accepts the page."
|
||
fi
|
||
|
||
# Decodes the session log that the action writes but never prints.
|
||
- name: Claude run post-mortem
|
||
if: ${{ always() }}
|
||
id: postmortem
|
||
uses: ./.github/actions/claude-postmortem
|
||
with:
|
||
execution_file: ${{ steps.claude.outputs.execution_file }}
|
||
label: Prepare What's New
|
||
|
||
# Re-probe after a failure: a pre-flight that was OK plus a post-flight that is not
|
||
# means the quota ran out during this very run.
|
||
- name: Re-check Claude credentials and quota after a failure
|
||
if: ${{ failure() }}
|
||
uses: ./.github/actions/claude-preflight
|
||
with:
|
||
base_url: ${{ secrets.PROXY_URL }}
|
||
jwt: ${{ secrets.ANTHROPIC_API_KEY }}
|
||
label: post-failure
|
||
|
||
- name: Upload the Claude execution log
|
||
if: ${{ always() && steps.claude.outputs.execution_file != '' }}
|
||
uses: actions/upload-artifact@v4
|
||
with:
|
||
name: claude-execution-log
|
||
path: ${{ steps.claude.outputs.execution_file }}
|
||
if-no-files-found: ignore
|
||
retention-days: 14
|
||
|
||
- name: Comment rendered preview link on the PR
|
||
if: ${{ always() }}
|
||
uses: actions/github-script@v7
|
||
with:
|
||
script: |
|
||
// Only ever preview the page this workflow updates: whatsnew-tbr.html.
|
||
const branch = 'whatsnew-tbr';
|
||
const file = 'src/main/resources/whatsnew-tbr.html';
|
||
const { owner, repo } = context.repo;
|
||
|
||
const prs = await github.rest.pulls.list({ owner, repo, state: 'open', head: `${owner}:${branch}` });
|
||
if (prs.data.length === 0) {
|
||
core.info(`No open PR for ${branch}; nothing to preview.`);
|
||
return;
|
||
}
|
||
const pr = prs.data[0];
|
||
|
||
// The page is self-contained (inline CSS), so raw.githack.com renders it directly.
|
||
const url = `https://raw.githack.com/${owner}/${repo}/${branch}/${file}`;
|
||
const marker = '<!-- whatsnew-preview -->';
|
||
const body = [
|
||
marker,
|
||
"### 📰 What's New preview",
|
||
'',
|
||
`Rendered via raw.githack.com: [\`${file}\`](${url})`,
|
||
'',
|
||
'> Note: the live page follows the **IDE** theme. This preview follows your ' +
|
||
'**OS** theme (the `__THEME__` token is unresolved here), and `__VERSION__` ' +
|
||
'shows literally — both are substituted by the IDE at runtime.',
|
||
].join('\n');
|
||
|
||
// Upsert a single sticky comment instead of spamming on re-runs.
|
||
const comments = await github.paginate(github.rest.issues.listComments, {
|
||
owner, repo, issue_number: pr.number,
|
||
});
|
||
const existing = comments.find(c => c.body && c.body.includes(marker));
|
||
if (existing) {
|
||
await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body });
|
||
} else {
|
||
await github.rest.issues.createComment({ owner, repo, issue_number: pr.number, body });
|
||
}
|