zvidlib 0.4.1

Frame-accurate video and audio I/O for native and WebAssembly applications
Documentation
name: Release

# Prepares a release from the Actions tab: sets the workspace version, promotes
# the changelog, pushes both to `main` with the tag, and hands the tag to
# Finalize release (`finalize-release.yml`), which packages and publishes it.
on:
  workflow_dispatch:
    inputs:
      level:
        description: Release level
        required: true
        type: choice
        default: patch
        options:
          - major
          - minor
          - patch
          - version
      version:
        description: Version to release (if level is "version"), for example 1.0.0
        required: false
        type: string

permissions:
  actions: write
  contents: write

jobs:
  release:
    name: Prepare the release
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ref: main
          fetch-depth: 0
      - name: Select the release version
        env:
          LEVEL: ${{ inputs.level }}
          VERSION_INPUT: ${{ inputs.version }}
        run: |
          set -euo pipefail
          if [ "$LEVEL" = "version" ]; then
            version="${VERSION_INPUT#v}"
          else
            latest="$(git tag -l 'v*' --sort=-v:refname | head -n1)"
            latest="${latest:-v0.0.0}"
            [[ "$latest" =~ ^v([0-9]+)\.([0-9]+)\.([0-9]+)$ ]] || { echo "invalid latest tag: $latest" >&2; exit 1; }
            major="${BASH_REMATCH[1]}"
            minor="${BASH_REMATCH[2]}"
            patch="${BASH_REMATCH[3]}"
            case "$LEVEL" in
              major) major=$((major + 1)); minor=0; patch=0 ;;
              minor) minor=$((minor + 1)); patch=0 ;;
              patch) patch=$((patch + 1)) ;;
              *) echo "invalid release level: $LEVEL" >&2; exit 1 ;;
            esac
            version="${major}.${minor}.${patch}"
          fi
          [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo "invalid release version: $version" >&2; exit 1; }
          echo "VERSION=$version" >> "$GITHUB_ENV"
          echo "TAG=v$version" >> "$GITHUB_ENV"
      # Every package takes its version from `[workspace.package]`, and each
      # `[workspace.dependencies]` entry pins its crate to exactly that version
      # (#604), so these are the only version strings to change. `Cargo.lock`
      # is not committed, so there is no lockfile to update.
      - name: Set the workspace version
        run: |
          set -euo pipefail
          current="$(awk '
            /^\[/ { in_package = ($0 == "[workspace.package]") }
            in_package && /^version = "/ { gsub(/^version = "|"$/, ""); print; exit }
          ' Cargo.toml)"
          [ -n "$current" ] || { echo "Cargo.toml has no [workspace.package] version" >&2; exit 1; }
          if [ "$current" != "$VERSION" ]; then
            sed -i \
              -e "s/^version = \"${current}\"\$/version = \"${VERSION}\"/" \
              -e "s/version = \"=${current}\"/version = \"=${VERSION}\"/" \
              Cargo.toml
            if grep -q "\"=${current}\"" Cargo.toml; then
              echo "Cargo.toml still pins a crate to ${current}" >&2
              exit 1
            fi
          fi
      # Renames `## Unreleased` to the release's heading and opens a new empty
      # `## Unreleased` above it. A changelog that already has the heading was
      # promoted by an earlier run and is left alone.
      - name: Promote the changelog
        run: |
          set -euo pipefail
          if grep -q "^## ${VERSION} - " CHANGELOG.md; then
            exit 0
          fi
          grep -q '^## Unreleased$' CHANGELOG.md || { echo "CHANGELOG.md has no ## Unreleased section" >&2; exit 1; }
          awk -v heading="## ${VERSION} - $(date -u +%Y-%m-%d)" '
            !done && $0 == "## Unreleased" { print; print ""; print heading; done = 1; next }
            { print }
          ' CHANGELOG.md > "$RUNNER_TEMP/CHANGELOG.md"
          mv "$RUNNER_TEMP/CHANGELOG.md" CHANGELOG.md
      - name: Push the release commit and tag
        run: |
          set -euo pipefail
          git config user.name 'github-actions[bot]'
          git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
          git add Cargo.toml CHANGELOG.md
          if ! git diff --cached --quiet; then
            git commit -m "Release zvidlib ${VERSION}"
            git push origin HEAD:main
          fi
          if ! git ls-remote --exit-code --tags origin "refs/tags/${TAG}" > /dev/null 2>&1; then
            git tag "$TAG"
            git push origin "$TAG"
          fi
      # A tag pushed with `GITHUB_TOKEN` starts no workflow, so Finalize
      # release is started on it explicitly.
      - name: Start Finalize release
        env:
          GH_TOKEN: ${{ github.token }}
        run: gh workflow run finalize-release.yml --repo "$GITHUB_REPOSITORY" --ref "$TAG" -f tag="$TAG"