|
| 1 | +name: "Bump version" |
| 2 | +description: > |
| 3 | + Bump a Python project's version using `uv version --bump`, |
| 4 | + update the CHANGELOG via towncrier, commit, tag, push, |
| 5 | + and optionally land a second commit bumping main onto a pre-release version. |
| 6 | +
|
| 7 | +# Vendored from climate-resource/github-actions bump-version so this public repo |
| 8 | +# does not depend on a private actions repo. Keep behaviour in sync with the |
| 9 | +# upstream action when updating. |
| 10 | + |
| 11 | +inputs: |
| 12 | + bump-rule: |
| 13 | + description: > |
| 14 | + Whitespace-separated list of segments passed to `uv version --bump`. |
| 15 | + Each segment becomes a separate `--bump <segment>` argument, |
| 16 | + so values like `patch`, `minor`, `major`, `stable`, `minor alpha`, `patch rc`, or `major beta` are all valid. |
| 17 | + uv requires a release component (patch, minor, major, stable) alongside any prerelease segment (alpha, beta, rc, dev) when the current version is a stable release. |
| 18 | + required: true |
| 19 | + pre-release-bump: |
| 20 | + description: > |
| 21 | + Pre-release segment to bump main to after tagging, so future commits do not share the tagged version. |
| 22 | +
|
| 23 | + One of: dev, alpha, beta, rc, none. |
| 24 | +
|
| 25 | + Use 'none' to skip the second commit. |
| 26 | + required: false |
| 27 | + default: "dev" |
| 28 | + pre-release-base: |
| 29 | + description: > |
| 30 | + Bump rule applied to the base version before adding the pre-release |
| 31 | + segment in the second commit. Defaults to 'patch'. |
| 32 | +
|
| 33 | + Use 'none' to skip bumping the base (only add the pre-release segment). |
| 34 | + required: false |
| 35 | + default: "patch" |
| 36 | + update-changelog: |
| 37 | + description: "If 'true', run towncrier to build the CHANGELOG before tagging." |
| 38 | + required: false |
| 39 | + default: "true" |
| 40 | + commit-email: |
| 41 | + description: "Email for the CI commit author." |
| 42 | + required: false |
| 43 | + default: "ci-runner@climate-resource.invalid" |
| 44 | + workspace-packages: |
| 45 | + description: > |
| 46 | + Optional newline-separated list of uv workspace package names to mirror |
| 47 | + the bumped version onto (in addition to the root project). |
| 48 | + required: false |
| 49 | + default: "" |
| 50 | + lock: |
| 51 | + description: "If 'true', run `uv lock` after each version change." |
| 52 | + required: false |
| 53 | + default: "true" |
| 54 | + pre-commit-command: |
| 55 | + description: > |
| 56 | + Optional shell command run after the changelog build and before each bump commit. |
| 57 | +
|
| 58 | + Use it to regenerate version-derived files (e.g. an OpenAPI schema) so they stay in sync in the tagged commit. |
| 59 | + The commit uses `git commit -a`, so any files the command regenerates are picked up automatically. |
| 60 | + The command must succeed; a non-zero exit aborts the bump. |
| 61 | + required: false |
| 62 | + default: "" |
| 63 | + pre-commit-skip: |
| 64 | + description: "If 'true', pass -n to git commit to skip pre-commit hooks." |
| 65 | + required: false |
| 66 | + default: "false" |
| 67 | + push: |
| 68 | + description: "If 'true', push the bump commit, tag, and pre-release commit." |
| 69 | + required: false |
| 70 | + default: "true" |
| 71 | + |
| 72 | +outputs: |
| 73 | + base-version: |
| 74 | + description: "The version before bumping." |
| 75 | + value: ${{ steps.bump.outputs.base-version }} |
| 76 | + new-version: |
| 77 | + description: "The new tagged version (no 'v' prefix)." |
| 78 | + value: ${{ steps.bump.outputs.new-version }} |
| 79 | + tag: |
| 80 | + description: "The new git tag (with 'v' prefix)." |
| 81 | + value: ${{ steps.bump.outputs.tag }} |
| 82 | + dev-version: |
| 83 | + description: "The pre-release version landed on main after tagging (if any)." |
| 84 | + value: ${{ steps.bump.outputs.dev-version }} |
| 85 | + is-prerelease: |
| 86 | + description: "'true' when the tagged version is a pre-release (a|b|rc|dev)." |
| 87 | + value: ${{ steps.bump.outputs.is-prerelease }} |
| 88 | + |
| 89 | +runs: |
| 90 | + using: "composite" |
| 91 | + steps: |
| 92 | + - name: Configure git identity |
| 93 | + shell: bash |
| 94 | + run: | |
| 95 | + git config --global user.name "${GITHUB_ACTOR}" |
| 96 | + git config --global user.email "${{ inputs.commit-email }}" |
| 97 | +
|
| 98 | + - name: Bump, changelog, commit, tag |
| 99 | + id: bump |
| 100 | + shell: bash |
| 101 | + env: |
| 102 | + BUMP_RULE: ${{ inputs.bump-rule }} |
| 103 | + PRE_RELEASE_BUMP: ${{ inputs.pre-release-bump }} |
| 104 | + PRE_RELEASE_BASE: ${{ inputs.pre-release-base }} |
| 105 | + UPDATE_CHANGELOG: ${{ inputs.update-changelog }} |
| 106 | + WORKSPACE_PACKAGES: ${{ inputs.workspace-packages }} |
| 107 | + RUN_LOCK: ${{ inputs.lock }} |
| 108 | + PRE_COMMIT_COMMAND: ${{ inputs.pre-commit-command }} |
| 109 | + COMMIT_SKIP_HOOKS: ${{ inputs.pre-commit-skip }} |
| 110 | + DO_PUSH: ${{ inputs.push }} |
| 111 | + run: | |
| 112 | + set -euo pipefail |
| 113 | +
|
| 114 | + commit_args=() |
| 115 | + if [[ "${COMMIT_SKIP_HOOKS}" == "true" ]]; then |
| 116 | + commit_args+=(-n) |
| 117 | + fi |
| 118 | +
|
| 119 | + mirror_workspace() { |
| 120 | + local version="$1" |
| 121 | + if [[ -z "${WORKSPACE_PACKAGES//[[:space:]]/}" ]]; then |
| 122 | + return 0 |
| 123 | + fi |
| 124 | + while IFS= read -r pkg; do |
| 125 | + pkg="${pkg// /}" |
| 126 | + [[ -z "${pkg}" ]] && continue |
| 127 | + echo "Mirroring version ${version} onto workspace package ${pkg}" |
| 128 | + uv version --frozen --package "${pkg}" "${version}" |
| 129 | + done <<< "${WORKSPACE_PACKAGES}" |
| 130 | + } |
| 131 | +
|
| 132 | + maybe_lock() { |
| 133 | + if [[ "${RUN_LOCK}" == "true" ]]; then |
| 134 | + uv lock |
| 135 | + fi |
| 136 | + } |
| 137 | +
|
| 138 | + run_pre_commit_command() { |
| 139 | + if [[ -n "${PRE_COMMIT_COMMAND}" ]]; then |
| 140 | + echo "Running pre-commit command" |
| 141 | + bash -c "${PRE_COMMIT_COMMAND}" |
| 142 | + fi |
| 143 | + } |
| 144 | +
|
| 145 | + BASE_VERSION="$(uv version --short)" |
| 146 | + echo "Bumping from version ${BASE_VERSION}" |
| 147 | +
|
| 148 | + read -r -a bump_segments <<< "${BUMP_RULE}" |
| 149 | + if [[ ${#bump_segments[@]} -eq 0 ]]; then |
| 150 | + echo "bump-rule must not be empty" >&2 |
| 151 | + exit 1 |
| 152 | + fi |
| 153 | + bump_cli_args=() |
| 154 | + for seg in "${bump_segments[@]}"; do |
| 155 | + bump_cli_args+=(--bump "${seg}") |
| 156 | + done |
| 157 | +
|
| 158 | + uv version --frozen "${bump_cli_args[@]}" |
| 159 | + NEW_VERSION="$(uv version --short)" |
| 160 | + echo "Bumped to version ${NEW_VERSION}" |
| 161 | +
|
| 162 | + mirror_workspace "${NEW_VERSION}" |
| 163 | + maybe_lock |
| 164 | +
|
| 165 | + if [[ "${UPDATE_CHANGELOG}" == "true" ]]; then |
| 166 | + uv run towncrier build --yes --version "v${NEW_VERSION}" |
| 167 | + else |
| 168 | + echo "Skipping changelog update" |
| 169 | + fi |
| 170 | +
|
| 171 | + run_pre_commit_command |
| 172 | +
|
| 173 | + git commit "${commit_args[@]}" -a -m "bump: version ${BASE_VERSION} -> ${NEW_VERSION}" |
| 174 | + git tag "v${NEW_VERSION}" |
| 175 | +
|
| 176 | + if [[ "${DO_PUSH}" == "true" ]]; then |
| 177 | + git push |
| 178 | + git push --tags |
| 179 | + fi |
| 180 | +
|
| 181 | + if [[ "${NEW_VERSION}" =~ (a|b|rc|dev) ]]; then |
| 182 | + IS_PRERELEASE="true" |
| 183 | + else |
| 184 | + IS_PRERELEASE="false" |
| 185 | + fi |
| 186 | +
|
| 187 | + echo "base-version=${BASE_VERSION}" >> "${GITHUB_OUTPUT}" |
| 188 | + echo "new-version=${NEW_VERSION}" >> "${GITHUB_OUTPUT}" |
| 189 | + echo "tag=v${NEW_VERSION}" >> "${GITHUB_OUTPUT}" |
| 190 | + echo "is-prerelease=${IS_PRERELEASE}" >> "${GITHUB_OUTPUT}" |
| 191 | +
|
| 192 | + # Skip the dev bump if the tagged version is already a pre-release or |
| 193 | + # the caller opted out. |
| 194 | + if [[ "${PRE_RELEASE_BUMP}" == "none" ]]; then |
| 195 | + echo "Skipping pre-release bump (pre-release-bump=none)" |
| 196 | + exit 0 |
| 197 | + fi |
| 198 | + if [[ "${IS_PRERELEASE}" == "true" ]]; then |
| 199 | + echo "Skipping pre-release bump; tagged version is already a pre-release" |
| 200 | + exit 0 |
| 201 | + fi |
| 202 | +
|
| 203 | + PREV_VERSION="${NEW_VERSION}" |
| 204 | + bump_args=() |
| 205 | + if [[ "${PRE_RELEASE_BASE}" != "none" ]]; then |
| 206 | + bump_args+=(--bump "${PRE_RELEASE_BASE}") |
| 207 | + fi |
| 208 | + bump_args+=(--bump "${PRE_RELEASE_BUMP}") |
| 209 | + uv version --frozen "${bump_args[@]}" |
| 210 | +
|
| 211 | + DEV_VERSION="$(uv version --short)" |
| 212 | + echo "Bumping main onto pre-release ${PREV_VERSION} > ${DEV_VERSION}" |
| 213 | +
|
| 214 | + mirror_workspace "${DEV_VERSION}" |
| 215 | + maybe_lock |
| 216 | + run_pre_commit_command |
| 217 | +
|
| 218 | + git commit "${commit_args[@]}" -a -m "bump(${PRE_RELEASE_BUMP}): version ${PREV_VERSION} > ${DEV_VERSION}" |
| 219 | +
|
| 220 | + if [[ "${DO_PUSH}" == "true" ]]; then |
| 221 | + git push |
| 222 | + fi |
| 223 | +
|
| 224 | + echo "dev-version=${DEV_VERSION}" >> "${GITHUB_OUTPUT}" |
0 commit comments