lab-ops 0.1.23

Personal utility tools for my homelab
Documentation
name: Patch Version

on:
  workflow_call:
    inputs:
      version:
        description: 'Version string to patch into Cargo.toml'
        required: false
        type: string
        default: ''
    outputs:
      version:
        description: 'The version that was patched'
        value: ${{ jobs.patch.outputs.version }}

jobs:
  patch:
    name: Patch Cargo.toml Version
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.set-version.outputs.version }}
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
          fetch-tags: true

      - name: Fetch All Tags
        run: git fetch --tags --force origin '+refs/tags/*:refs/tags/*'

      - name: Determine version
        id: set-version
        run: |
          if [ -n "${{ inputs.version }}" ]; then
            VERSION="${{ inputs.version }}"
          else
            LATEST_TAG=$(git tag -l 'v*' --sort=-version:refname | head -1)
            VERSION="${LATEST_TAG:-}"
            if [ -z "$VERSION" ]; then
              VERSION=$(grep '^version = ' Cargo.toml | head -n1 | sed 's/version = "\(.*\)"/\1/')
            fi
          fi
          echo "version=$VERSION" >> "$GITHUB_OUTPUT"
          echo "Patching version: $VERSION"

      - name: Patch root Cargo.toml
        run: |
          VERSION=${{ steps.set-version.outputs.version }}
          VERSION=${VERSION#v}
          sed -i "0,/^version = \".*\"/{s/^version = \".*\"/version = \"$VERSION\"/}" Cargo.toml

      - name: Patch workspace members
        id: workspace
        run: |
          if ! grep -q '^\[workspace\]' Cargo.toml; then
            echo "No workspace section found — skipping member patching"
            exit 0
          fi

          VERSION=${{ steps.set-version.outputs.version }}
          VERSION=${VERSION#v}

          # Find workspace member directories (convention: listed under crates/)
          MEMBERS=$(find crates -maxdepth 2 -name Cargo.toml -exec dirname {} \; | sed 's|^crates/||' | sort -u)
          if [ -z "$MEMBERS" ]; then
            echo "No workspace members found under crates/"
            exit 0
          fi

          # Determine previous release tag
          TAGS=$(git tag -l 'v*' --sort=-version:refname)
          TAG_COUNT=$(echo "$TAGS" | grep -c . || true)
          if [ "$TAG_COUNT" -lt 1 ]; then
            # First release: bump all members
            CHANGED="$MEMBERS"
            echo "No previous tag found — bumping all members"
          else
            PREV_TAG=$(echo "$TAGS" | head -1)
            echo "Previous tag: $PREV_TAG"

            # Detect members with file changes since last tag
            CHANGED=""
            for member in $MEMBERS; do
              if git diff --name-only "$PREV_TAG"..HEAD -- "crates/$member/" | grep -q .; then
                CHANGED="$CHANGED $member"
              fi
            done

            # If nothing changed in subcrates, bump the root only
            if [ -z "$CHANGED" ]; then
              echo "No member changes detected — root only"
              exit 0
            fi
          fi

          echo "members=$CHANGED" >> "$GITHUB_OUTPUT"
          echo "Patching changed members: $CHANGED"

          for member in $CHANGED; do
            CURRENT=$(grep '^version = ' "crates/$member/Cargo.toml" | head -n1 | sed 's/version = "\(.*\)"/\1/')
            BUMP="patch"

            if [ -n "$PREV_TAG" ]; then
              while IFS= read -r commit; do
                if echo "$commit" | grep -q '^chore!(major)'; then
                  BUMP="major"; break
                elif echo "$commit" | grep -q '^chore!(minor)'; then
                  BUMP="minor"
                fi
              done < <(git log "$PREV_TAG..HEAD" --no-merges --format="%s" -- "crates/$member/")
            fi

            MAJOR=$(echo "$CURRENT" | cut -d. -f1)
            MINOR=$(echo "$CURRENT" | cut -d. -f2)
            PATCH=$(echo "$CURRENT" | cut -d. -f3)
            case "$BUMP" in
              major) MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0 ;;
              minor) MINOR=$((MINOR + 1)); PATCH=0 ;;
              patch) PATCH=$((PATCH + 1)) ;;
            esac
            MEMBER_VERSION="$MAJOR.$MINOR.$PATCH"

            echo "  crates/$member: $CURRENT -> $MEMBER_VERSION ($BUMP)"
            sed -i "0,/^version = \".*\"/{s/^version = \".*\"/version = \"$MEMBER_VERSION\"/}" "crates/$member/Cargo.toml"

            if grep -q "^$member = {" Cargo.toml; then
              echo "  Updating workspace dependency: $member -> $MEMBER_VERSION"
              sed -i "/^$member = {/,/}/s/version = \"[^\"]*\"/version = \"$MEMBER_VERSION\"/" Cargo.toml
            fi
          done

      - name: Upload patched Cargo.tomls
        uses: actions/upload-artifact@v4
        with:
          name: patched-cargo-toml
          path: |
            Cargo.toml
            crates/**/Cargo.toml