feat: add release-fix subcommand to release script#12272
feat: add release-fix subcommand to release script#12272kaladinlight merged 4 commits intodevelopfrom
Conversation
Adds a "Release fix" option that cherry-picks selected develop commits onto the release branch with -x when a release PR is already open. Also adds a safety check in handleReleaseReady that warns about cherry-picks missing the -x trailer. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughThe release script adds a "Release fix" workflow to cherry-pick selected develop commits onto the release branch via a temporary worktree, parameterizes the commit selection prompt, and adds a pre-flight scan that warns about release commits missing cherry-pick trailers. Changes
Sequence DiagramsequenceDiagram
participant User
participant CLI as Release Script
participant Git as Git/Remote
participant Worktree as Temp Worktree
User->>CLI: choose "Release fix"
CLI->>Git: verify open release→main PR
Git-->>CLI: PR exists / missing
CLI->>Git: list unreleased commits (origin/release...origin/develop)
Git-->>CLI: commits list
CLI->>User: prompt selection (custom message)
User->>CLI: select commits
CLI->>User: confirm selection
User->>CLI: confirm
CLI->>Worktree: create temporary worktree on release
CLI->>Worktree: cherry-pick selected commits with -x
Worktree-->>Git: apply commits to release branch
CLI->>Git: push updated release branch
CLI->>Worktree: cleanup worktree
CLI->>User: complete / report errors
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
scripts/release.ts (2)
39-40: Rename this constant toRELEASE_TYPE_OPTIONS(or similar).Since this declaration is being touched anyway, it would be a good time to align it with the repo’s constant naming convention.
As per coding guidelines, "Use UPPER_SNAKE_CASE for constants and configuration values with descriptive names".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/release.ts` around lines 39 - 40, Rename the exported constant releaseType to RELEASE_TYPE_OPTIONS (or another UPPER_SNAKE_CASE name) and keep it declared as a const assertion; then update the associated type alias ReleaseType to reference the new symbol (i.e., type ReleaseType = (typeof RELEASE_TYPE_OPTIONS)[number]) and update any usages of releaseType to the new constant name throughout the module.
761-774: Consider reusing a shared helper for%H %sparsing.This block duplicates the parsing logic from
getUnreleasedCommits(). Extracting agetUnreleasedCommitsBetween(baseRef, headRef)helper would keep hotfix/release-fix selection consistent if the log format ever changes.Refactor sketch
+const getUnreleasedCommitsBetween = async ( + baseRef: string, + headRef: string, +): Promise<UnreleasedCommit[]> => { + const result = await pify(exec)( + `git log --cherry-pick --right-only --no-merges --pretty=format:"%H %s" ${baseRef}...${headRef}`, + ).catch(() => '') + if (!result.trim()) return [] + + return result + .trim() + .split('\n') + .filter(Boolean) + .map((line: string) => { + const spaceIdx = line.indexOf(' ') + if (spaceIdx === -1) return { hash: line, message: '' } + return { hash: line.slice(0, spaceIdx), message: line.slice(spaceIdx + 1) } + }) +} - - const result = await pify(exec)( - `git log --cherry-pick --right-only --no-merges --pretty=format:"%H %s" origin/release...origin/develop`, - ).catch(() => '') - const unreleased: UnreleasedCommit[] = !result.trim() - ? [] - : result - .trim() - .split('\n') - .filter(Boolean) - .map((line: string) => { - const spaceIdx = line.indexOf(' ') - if (spaceIdx === -1) return { hash: line, message: '' } - return { hash: line.slice(0, spaceIdx), message: line.slice(spaceIdx + 1) } - }) + const unreleased = await getUnreleasedCommitsBetween('origin/release', 'origin/develop')🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/release.ts` around lines 761 - 774, This duplicates the commit-parsing logic; extract a shared helper (e.g., getUnreleasedCommitsBetween(baseRef: string, headRef: string): Promise<UnreleasedCommit[]>) that runs the git log command and parses lines of "%H %s" into {hash,message} objects, then replace the inline logic here and in getUnreleasedCommits() to call that helper so both hotfix/release-fix selection use the same parsing and return type; ensure the helper trims empty output, handles lines without spaces, and returns an empty array on errors.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@scripts/release.ts`:
- Around line 611-639: The current cherry-pick heuristic uses `isFromMerge`
computed with `git log ... origin/main...${sha}` which compares a branch to a
single commit and can false-positive normal release commits; change the check to
test reachability from the develop branch instead. Replace the `isFromMerge`
invocation with a reachability/ancestor test such as running `git merge-base
--is-ancestor ${sha} origin/develop` (or equivalent `git rev-list
--ancestry-path`/`git branch --contains`) and treat the commit as coming from
develop when that command exits 0; keep using the same `sha`, `body`, and
warning logic so only commits that are not ancestors of `origin/develop` and
also lack the "(cherry picked from commit" trailer trigger the yellow -x
warning.
---
Nitpick comments:
In `@scripts/release.ts`:
- Around line 39-40: Rename the exported constant releaseType to
RELEASE_TYPE_OPTIONS (or another UPPER_SNAKE_CASE name) and keep it declared as
a const assertion; then update the associated type alias ReleaseType to
reference the new symbol (i.e., type ReleaseType = (typeof
RELEASE_TYPE_OPTIONS)[number]) and update any usages of releaseType to the new
constant name throughout the module.
- Around line 761-774: This duplicates the commit-parsing logic; extract a
shared helper (e.g., getUnreleasedCommitsBetween(baseRef: string, headRef:
string): Promise<UnreleasedCommit[]>) that runs the git log command and parses
lines of "%H %s" into {hash,message} objects, then replace the inline logic here
and in getUnreleasedCommits() to call that helper so both hotfix/release-fix
selection use the same parsing and return type; ensure the helper trims empty
output, handles lines without spaces, and returns an empty array on errors.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
Use `git merge-base --is-ancestor` to check if a commit on release is reachable from develop, rather than the previous left-only log heuristic which could false-positive on normal release commits. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
scripts/release.ts (2)
39-40: RenamereleaseTypeto a constant-style identifier.This is a static choice list, so
RELEASE_TYPESwould align better with the repo’s constant/config naming rule.As per coding guidelines, "Use UPPER_SNAKE_CASE for constants and configuration values with descriptive names."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/release.ts` around lines 39 - 40, Rename the mutable-looking identifier releaseType to a constant-style name RELEASE_TYPES and update its type alias reference accordingly (currently ReleaseType uses (typeof releaseType)[number]); change the const declaration to const RELEASE_TYPES = ['Regular', 'Hotfix', 'Release fix'] as const and update the type alias to type ReleaseType = (typeof RELEASE_TYPES)[number] so the code follows UPPER_SNAKE_CASE for constant/config values while preserving the ReleaseType type.
762-775: Extract the shared unreleased-commit query/parser.This reimplements the same
git logparsing asgetUnreleasedCommits(), with only the rev range changed. Pull the range into a parameter so the hotfix and release-fix paths stay aligned.♻️ Possible extraction
-const getUnreleasedCommits = async (): Promise<UnreleasedCommit[]> => { +const getUnreleasedCommits = async ( + baseRef = 'origin/main', + compareRef = 'origin/develop', +): Promise<UnreleasedCommit[]> => { const result = await pify(exec)( - `git log --cherry-pick --right-only --no-merges --pretty=format:"%H %s" origin/main...origin/develop`, + `git log --cherry-pick --right-only --no-merges --pretty=format:"%H %s" ${baseRef}...${compareRef}`, ).catch(() => '') if (!result.trim()) return [] return result .trim() .split('\n') .filter(Boolean) .map((line: string) => { const spaceIdx = line.indexOf(' ') if (spaceIdx === -1) return { hash: line, message: '' } return { hash: line.slice(0, spaceIdx), message: line.slice(spaceIdx + 1) } }) }- const result = await pify(exec)( - `git log --cherry-pick --right-only --no-merges --pretty=format:"%H %s" origin/release...origin/develop`, - ).catch(() => '') - const unreleased: UnreleasedCommit[] = !result.trim() - ? [] - : result - .trim() - .split('\n') - .filter(Boolean) - .map((line: string) => { - const spaceIdx = line.indexOf(' ') - if (spaceIdx === -1) return { hash: line, message: '' } - return { hash: line.slice(0, spaceIdx), message: line.slice(spaceIdx + 1) } - }) + const unreleased = await getUnreleasedCommits('origin/release', 'origin/develop')🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/release.ts` around lines 762 - 775, Duplicate logic for running and parsing the `git log` output should be extracted into a shared helper so both the hotfix and release paths stay aligned: create or update a function (e.g., getUnreleasedCommits(range: string) or parseUnreleasedCommits(output: string) called by getUnreleasedCommits) that accepts the rev-range string, runs the `git log --cherry-pick --right-only --no-merges --pretty=format:"%H %s" <range>` command (or parses its output), and returns UnreleasedCommit[] by splitting lines, finding the first space to separate hash and message, and handling missing message cases; replace the duplicated block with a call to this helper from both places (the existing getUnreleasedCommits usage and the hotfix/release-fix code paths) so only the range differs between callers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@scripts/release.ts`:
- Around line 799-813: After successfully pushing the fix commits and removing
the worktree in the try/catch block (around wt.push and the subsequent git
worktree remove calls), update the open release PR body so the new commits
appear in the release notes/testing checklist: call the existing routine that
generates the release PR body (invoke handleReleaseReady() or extract its
body-generation logic) or add a new helper (e.g., updateReleasePRBody(prNumber,
generatedBody)) to PATCH the PR via the GitHub API before exiting;
alternatively, if you want to force manual intervention, block exit with a clear
message instructing the user to update the PR body manually instead of silently
returning — ensure this change is placed just after the successful push and
before the final console.log so release_open() won't skip regenerating content.
---
Nitpick comments:
In `@scripts/release.ts`:
- Around line 39-40: Rename the mutable-looking identifier releaseType to a
constant-style name RELEASE_TYPES and update its type alias reference
accordingly (currently ReleaseType uses (typeof releaseType)[number]); change
the const declaration to const RELEASE_TYPES = ['Regular', 'Hotfix', 'Release
fix'] as const and update the type alias to type ReleaseType = (typeof
RELEASE_TYPES)[number] so the code follows UPPER_SNAKE_CASE for constant/config
values while preserving the ReleaseType type.
- Around line 762-775: Duplicate logic for running and parsing the `git log`
output should be extracted into a shared helper so both the hotfix and release
paths stay aligned: create or update a function (e.g.,
getUnreleasedCommits(range: string) or parseUnreleasedCommits(output: string)
called by getUnreleasedCommits) that accepts the rev-range string, runs the `git
log --cherry-pick --right-only --no-merges --pretty=format:"%H %s" <range>`
command (or parses its output), and returns UnreleasedCommit[] by splitting
lines, finding the first space to separate hash and message, and handling
missing message cases; replace the duplicated block with a call to this helper
from both places (the existing getUnreleasedCommits usage and the
hotfix/release-fix code paths) so only the range differs between callers.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
Description
Adds a "Release fix" option to
pnpm releasethat cherry-picks selected develop commits onto the release branch with-xwhen a release PR is already open. This closes the last manual gap in the release flow - previously, cherry-picking a fix onto release required manually runninggit cherry-pick -xand remembering the-xflag.Also adds a safety check in
handleReleaseReadythat warns if any commit on release was cherry-picked without-x(missing the(cherry picked from commit ...)trailer), which would cause it to be re-included in a future release.Changes:
handleReleaseFixhandler: validates open release PR exists, shows only commits not yet on release (comparesorigin/release...origin/develop), cherry-picks with-xin a worktreehandleReleaseReadyfor missing cherry-pick trailersinquireSelectCommitsnow accepts a custom message parameter for context-appropriate promptsdefaultcase in main switchRisk
Low - internal tooling only, no production code changes.
Testing
Engineering
pnpm releaseand select "Release fix" with no open release PR - should errorpnpm releaseand select "Release fix" with an open release PR - should show only commits not already on release-xonto releaseOperations
Summary by CodeRabbit
New Features
Improvements