diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7f653e511..8bf6dad27 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,9 +16,10 @@ on: workflow_dispatch: inputs: version: - description: "Version to Release (e.g., v1.2.3)" - required: true + description: "Version to Release (x.y.z). Leave empty for auto patch bump." + required: false type: string + default: '' env: REGISTRY: "ghcr.io" @@ -31,6 +32,12 @@ env: # ============================================================================= jobs: + version: + name: Resolve Version + uses: ./.github/workflows/version.yml + with: + version: ${{ inputs.version }} + build_client: name: "Build C Client (${{ matrix.os }})" runs-on: ${{ matrix.os }} @@ -201,11 +208,9 @@ jobs: release: name: "Create Release" - needs: [build_client, create_universal_macos] + needs: [version, build_client, create_universal_macos] runs-on: ubuntu-latest - if: | - github.event_name == 'workflow_dispatch' && - inputs.version != '' + if: github.event_name == 'workflow_dispatch' steps: - name: Checkout uses: actions/checkout@v4 @@ -254,8 +259,8 @@ jobs: - name: Create Release uses: softprops/action-gh-release@v2 with: - tag_name: ${{ inputs.version }} - name: "🦩 ${{ inputs.version }}" + tag_name: ${{ needs.version.outputs.version }} + name: "🦩 ${{ needs.version.outputs.version }}" draft: false prerelease: false token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/version.yml b/.github/workflows/version.yml new file mode 100644 index 000000000..e45591fa6 --- /dev/null +++ b/.github/workflows/version.yml @@ -0,0 +1,63 @@ +name: Resolve Version + +on: + workflow_call: + inputs: + version: + description: "Version to release (x.y.z). Leave empty for auto patch bump." + required: false + type: string + default: '' + outputs: + version: + description: "Resolved release version" + value: ${{ jobs.version.outputs.version }} + +jobs: + version: + name: Resolve version + runs-on: ubuntu-latest + outputs: + version: ${{ steps.version_push.outputs.version || steps.version_dispatch.outputs.version }} + steps: + - name: Resolve version (push) + id: version_push + if: github.event_name != 'workflow_dispatch' + run: | + echo "push event: version resolves to latest" + echo "version=latest" >> "$GITHUB_OUTPUT" + + - name: Resolve version (dispatch) + id: version_dispatch + if: github.event_name == 'workflow_dispatch' + env: + INPUT_VERSION: ${{ inputs.version }} + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + run: | + set -euo pipefail + + latest=$(gh api "repos/$GITHUB_REPOSITORY/releases/latest" --jq .tag_name) + [[ "$latest" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo "Latest release tag '${latest}' is not x.y.z"; exit 1; } + IFS='.' read -r cur_major cur_minor cur_patch <<< "$latest" + + if [ -z "$INPUT_VERSION" ]; then + version="${cur_major}.${cur_minor}.$((cur_patch + 1))" + echo "No version provided, auto patch bump: ${latest} -> ${version}" + else + if ! [[ "$INPUT_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Invalid version format: ${INPUT_VERSION} (expected x.y.z)" + exit 1 + fi + IFS='.' read -r new_major new_minor new_patch <<< "$INPUT_VERSION" + if ! ([ "$new_major" -eq $((cur_major + 1)) ] && [ "$new_minor" -eq 0 ] && [ "$new_patch" -eq 0 ]) && + ! ([ "$new_major" -eq "$cur_major" ] && [ "$new_minor" -eq $((cur_minor + 1)) ] && [ "$new_patch" -eq 0 ]) && + ! ([ "$new_major" -eq "$cur_major" ] && [ "$new_minor" -eq "$cur_minor" ] && [ "$new_patch" -eq $((cur_patch + 1)) ]); then + echo "Invalid version bump: ${latest} -> ${INPUT_VERSION} (expected $((cur_major + 1)).0.0, ${cur_major}.$((cur_minor + 1)).0, or ${cur_major}.${cur_minor}.$((cur_patch + 1)))" + exit 1 + fi + version="$INPUT_VERSION" + echo "Using provided version: ${version}" + fi + + echo "version=${version}" >> "$GITHUB_OUTPUT"