From d03655686b721cfeb6ae11bdcf29621486760771 Mon Sep 17 00:00:00 2001 From: William Aaron Cheung Date: Mon, 7 Sep 2026 20:42:34 +0800 Subject: [PATCH] feat(release-candidate): bump_command for lockfiles and lockstep versions A single version-file rewrite is not enough for most Rust repos: Cargo.lock must follow, and workspaces that pin path dependencies (mega-evm) carry the version in several lines. bump_command runs after the rewrite with OLD_VERSION/NEW_VERSION set; whatever it changes ships in the candidate commit. The action re-reads the version file afterwards so a command cannot silently undo the bump. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_013eLFMaDpEwgDzyDBzCQzLH --- .github/actions/README.md | 8 +++-- .github/actions/release-candidate/action.yml | 32 ++++++++++++++++++++ workflow-templates/release-candidate.yml | 3 ++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/.github/actions/README.md b/.github/actions/README.md index f2ed3e3..842a3fa 100644 --- a/.github/actions/README.md +++ b/.github/actions/README.md @@ -352,13 +352,17 @@ pr-review: Three composite actions implement the org release flow — trunk-first candidate, settle-by-PR, publish-once. They are language-agnostic: the only repo-specific inputs are where the version lives (`version_file` + -`version_pattern`: `plain`, `toml`, `json`) and the changelog path. Builds and +`version_pattern`: `plain`, `toml`, `json`), an optional `bump_command` for +whatever else must move with the version (`cargo update --workspace` for a +lockfile; path-dependency versions in a Cargo workspace — it runs with +`OLD_VERSION`/`NEW_VERSION` set, and the calling job installs the toolchain it +needs first), and the changelog path. Builds and artifact uploads are not part of them; a repo that ships binaries adds its own `push: tags` workflow, which the tag created by `release-publish` fires. | Action | Trigger in the consumer | Does | |---|---|---| -| `release-candidate` `stage: propose` | `workflow_dispatch` on the default branch | bumps `version_file`, drafts this release's changelog entry ("unreleased"), syncs the previous release's entry from its tag, opens `chore/release-candidate-vX.Y.Z` PR | +| `release-candidate` `stage: propose` | `workflow_dispatch` on the default branch | bumps `version_file`, runs `bump_command` (lockfile, path-dep versions), drafts this release's changelog entry ("unreleased"), syncs the previous release's entry from its tag, opens `chore/release-candidate-vX.Y.Z` PR | | `release-candidate` `stage: cut` | that PR merging | creates `release-vX.Y.Z` at the merge commit | | `release-settle` | `workflow_dispatch` with version + tip SHA | guards, regenerates the entry up to the tip and stamps the date, opens `chore/release-settle-vX.Y.Z` PR onto the release branch | | `release-publish` | the settle PR merging | annotated tag at the merge commit (refuses if it exists or the branch drifted), GitHub Release with the entry as notes | diff --git a/.github/actions/release-candidate/action.yml b/.github/actions/release-candidate/action.yml index 5224788..2e89e6a 100644 --- a/.github/actions/release-candidate/action.yml +++ b/.github/actions/release-candidate/action.yml @@ -30,6 +30,17 @@ inputs: description: "How the version is stored in `version_file`: plain, toml or json." required: false default: plain + bump_command: + description: >- + Optional shell command run after `version_file` is rewritten, for + anything else that must move with the version: lockfiles + (`cargo update --workspace`), path-dependency versions, generated + files. Runs with `OLD_VERSION` and `NEW_VERSION` in the environment, + in the repository root, under `bash -euo pipefail`. Whatever it + changes is committed with the bump. Install any toolchain it needs in + the calling job before this action. + required: false + default: "" release_branch_prefix: description: "Release branch name prefix; the branch is `X.Y.Z`." required: false @@ -159,6 +170,27 @@ runs: echo "$FILE: $old -> $VERSION" git diff --stat + - name: Run bump command + if: inputs.stage == 'propose' && inputs.bump_command != '' + shell: bash + env: + OLD_VERSION: ${{ steps.bump.outputs.old }} + NEW_VERSION: ${{ steps.version.outputs.version }} + BUMP_COMMAND: ${{ inputs.bump_command }} + FILE: ${{ inputs.version_file }} + PATTERN: ${{ inputs.version_pattern }} + TOOLS: ${{ github.action_path }}/../release-tools/release_tools.py + run: | + set -euo pipefail + echo "bump command: $BUMP_COMMAND" + bash -euo pipefail -c "$BUMP_COMMAND" + # The command must not undo the version-file rewrite. + actual="$(python3 "$TOOLS" read-file "$FILE" "$PATTERN")" + if [[ "$actual" != "$NEW_VERSION" ]]; then + echo "::error::after bump_command, $FILE reads $actual, expected $NEW_VERSION"; exit 1 + fi + git diff --stat + - name: Draft changelog if: inputs.stage == 'propose' && inputs.changelog_file != '' id: changelog diff --git a/workflow-templates/release-candidate.yml b/workflow-templates/release-candidate.yml index fa13f6d..18dd2c2 100644 --- a/workflow-templates/release-candidate.yml +++ b/workflow-templates/release-candidate.yml @@ -44,6 +44,9 @@ jobs: version_file: VERSION version_pattern: plain changelog_file: CHANGELOG.md + # Anything else that must move with the version, e.g. a lockfile: + # bump_command: cargo update --workspace + # (install the toolchain it needs in this job, before this step) cut: if: >-