Adjust auto release action
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Sat, 25 Oct 2025 09:54:40 +0000 (11:54 +0200)
committerMarcel van der Veldt <m.vanderveldt@outlook.com>
Sat, 25 Oct 2025 09:54:40 +0000 (11:54 +0200)
.github/workflows/auto-release-nightly.yml [deleted file]
.github/workflows/auto-release.yml [new file with mode: 0644]

diff --git a/.github/workflows/auto-release-nightly.yml b/.github/workflows/auto-release-nightly.yml
deleted file mode 100644 (file)
index 73b4e81..0000000
+++ /dev/null
@@ -1,113 +0,0 @@
-name: Auto Release
-
-# Automatically creates a nightly release every night at 02:00 UTC if there are 2+ commits since the last release
-# Calculates the next version number (patch increment) and triggers the publish release workflow
-
-on:
-  schedule:
-    # Run at 02:00 UTC every day
-    - cron: "0 2 * * *"
-  workflow_dispatch: # Allow manual trigger for testing
-
-permissions:
-  contents: write
-
-jobs:
-  check-and-release:
-    runs-on: ubuntu-latest
-    outputs:
-      version: ${{ steps.next_version.outputs.version }}
-      should_release: ${{ steps.check_commits.outputs.has_commits }}
-    steps:
-      - name: Checkout repository
-        uses: actions/checkout@v5
-        with:
-          fetch-depth: 0 # Fetch all history for proper comparison
-
-      - name: Check for new commits
-        id: check_commits
-        run: |
-          # Get the latest NIGHTLY/DEV release (exclude drafts, filter for .dev versions)
-          LATEST_RELEASE=$(gh release list --exclude-drafts --limit 100 --json createdAt,tagName,isPrerelease --jq '.[] | select(.tagName | contains(".dev"))' 2>/dev/null | jq -s '.[0]' || echo "")
-
-          if [ -z "$LATEST_RELEASE" ] || [ "$LATEST_RELEASE" == "null" ]; then
-            echo "No previous nightly releases found"
-            echo "has_commits=true" >> $GITHUB_OUTPUT
-            echo "last_tag=" >> $GITHUB_OUTPUT
-          else
-            RELEASE_DATE=$(echo "$LATEST_RELEASE" | jq -r '.createdAt')
-            LAST_TAG=$(echo "$LATEST_RELEASE" | jq -r '.tagName')
-            echo "Latest nightly release: $LAST_TAG at $RELEASE_DATE"
-            echo "last_tag=$LAST_TAG" >> $GITHUB_OUTPUT
-
-            # Check if there are commits since the latest nightly release
-            COMMITS_SINCE=$(git log --since="$RELEASE_DATE" --oneline | wc -l)
-            echo "Commits since last nightly release: $COMMITS_SINCE"
-
-            # Require at least 2 commits for auto-release
-            if [ "$COMMITS_SINCE" -ge 2 ]; then
-              echo "has_commits=true" >> $GITHUB_OUTPUT
-            else
-              echo "has_commits=false" >> $GITHUB_OUTPUT
-              echo "Only $COMMITS_SINCE commit(s) found. Need at least 2 commits for auto-release."
-            fi
-          fi
-        env:
-          GH_TOKEN: ${{ github.token }}
-
-      - name: Calculate next version
-        id: next_version
-        if: steps.check_commits.outputs.has_commits == 'true'
-        run: |
-          LAST_TAG="${{ steps.check_commits.outputs.last_tag }}"
-
-          # Get today's date in YYYYMMDD format and current hour (00-23) for uniqueness
-          TODAY=$(date -u +%Y%m%d)
-          HOUR=$(date -u +%H)
-
-          if [ -z "$LAST_TAG" ]; then
-            # No previous nightly tag, start with 0.0.1.devYYYYMMDDHH
-            NEW_VERSION="0.0.1.dev${TODAY}${HOUR}"
-          else
-            # Extract version number (handles tags like "v1.2.3.dev2025102514" or "1.2.3.dev20251023")
-            VERSION=$(echo "$LAST_TAG" | sed 's/^v//')
-
-            # Check if it's a .devYYYYMMDD version (with or without hour suffix)
-            if [[ "$VERSION" =~ ^([0-9]+\.[0-9]+\.[0-9]+)\.dev([0-9]+)$ ]]; then
-              BASE_VERSION="${BASH_REMATCH[1]}"
-
-              # Use today's date and current hour for the new dev version
-              NEW_VERSION="${BASE_VERSION}.dev${TODAY}${HOUR}"
-            else
-              # Fallback: treat as base version and add .devYYYYMMDDHH
-              NEW_VERSION="${VERSION}.dev${TODAY}${HOUR}"
-            fi
-          fi
-
-          echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT
-          echo "New nightly version: $NEW_VERSION"
-
-      - name: Log release decision
-        run: |
-          if [ "${{ steps.check_commits.outputs.has_commits }}" == "true" ]; then
-            echo "✅ Will create release ${{ steps.next_version.outputs.version }}"
-          else
-            echo "⏭️ Skipping release - not enough commits"
-          fi
-
-  trigger-release:
-    name: Trigger Release Workflow
-    needs: check-and-release
-    if: needs.check-and-release.outputs.should_release == 'true'
-    permissions:
-      contents: write
-      pull-requests: read
-      packages: write
-      id-token: write # Required for PyPI publishing
-    uses: ./.github/workflows/release.yml
-    with:
-      version: ${{ needs.check-and-release.outputs.version }}
-      channel: nightly
-    secrets:
-      PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
-      PRIVILEGED_GITHUB_TOKEN: ${{ secrets.PRIVILEGED_GITHUB_TOKEN }}
diff --git a/.github/workflows/auto-release.yml b/.github/workflows/auto-release.yml
new file mode 100644 (file)
index 0000000..d010fe1
--- /dev/null
@@ -0,0 +1,253 @@
+name: Auto Release
+
+# Automatically creates releases with proper version increments
+# - Nightly: runs at 02:00 UTC daily if there are 2+ commits (format: 1.2.3.dev20251025HH)
+# - Beta: manual trigger (format: 1.2.0b1, 1.2.0b2, etc.)
+# - Stable: manual trigger (format: 1.2.3, 1.2.4, etc.)
+
+on:
+  schedule:
+    # Run at 02:00 UTC every day for nightly releases
+    - cron: "0 2 * * *"
+  workflow_dispatch:
+    inputs:
+      channel:
+        description: "Release channel"
+        required: true
+        type: choice
+        options:
+          - nightly
+          - beta
+          - stable
+        default: nightly
+
+permissions:
+  contents: write
+
+jobs:
+  check-and-release:
+    runs-on: ubuntu-latest
+    outputs:
+      version: ${{ steps.next_version.outputs.version }}
+      should_release: ${{ steps.check_commits.outputs.has_commits }}
+      channel: ${{ steps.set_channel.outputs.channel }}
+    steps:
+      - name: Set release channel
+        id: set_channel
+        run: |
+          # Use input channel for manual runs, default to nightly for scheduled runs
+          if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
+            CHANNEL="${{ inputs.channel }}"
+          else
+            CHANNEL="nightly"
+          fi
+          echo "channel=$CHANNEL" >> $GITHUB_OUTPUT
+          echo "Release channel: $CHANNEL"
+
+      - name: Checkout repository
+        uses: actions/checkout@v5
+        with:
+          fetch-depth: 0 # Fetch all history for proper comparison
+
+      - name: Check for new commits
+        id: check_commits
+        run: |
+          CHANNEL="${{ steps.set_channel.outputs.channel }}"
+
+          # Define search patterns for each channel
+          case "$CHANNEL" in
+            nightly)
+              SEARCH_PATTERN=".dev"
+              ;;
+            beta)
+              SEARCH_PATTERN=".b"
+              ;;
+            stable)
+              # For stable, we want versions that don't contain .dev or .b
+              SEARCH_PATTERN="stable"
+              ;;
+          esac
+
+          # Get the latest release for the channel
+          if [ "$CHANNEL" = "stable" ]; then
+            # For stable, get releases that don't contain .dev or .b
+            LATEST_RELEASE=$(gh release list --exclude-drafts --limit 100 --json createdAt,tagName,isPrerelease --jq '.[] | select(.tagName | contains(".dev") | not) | select(.tagName | contains(".b") | not)' 2>/dev/null | jq -s '.[0]' || echo "")
+          else
+            # For nightly and beta, filter by pattern
+            LATEST_RELEASE=$(gh release list --exclude-drafts --limit 100 --json createdAt,tagName,isPrerelease --jq ".[] | select(.tagName | contains(\"$SEARCH_PATTERN\"))" 2>/dev/null | jq -s '.[0]' || echo "")
+          fi
+
+          if [ -z "$LATEST_RELEASE" ] || [ "$LATEST_RELEASE" == "null" ]; then
+            echo "No previous $CHANNEL releases found"
+            echo "has_commits=true" >> $GITHUB_OUTPUT
+            echo "last_tag=" >> $GITHUB_OUTPUT
+          else
+            RELEASE_DATE=$(echo "$LATEST_RELEASE" | jq -r '.createdAt')
+            LAST_TAG=$(echo "$LATEST_RELEASE" | jq -r '.tagName')
+            echo "Latest $CHANNEL release: $LAST_TAG at $RELEASE_DATE"
+            echo "last_tag=$LAST_TAG" >> $GITHUB_OUTPUT
+
+            # Check if there are commits since the latest release
+            COMMITS_SINCE=$(git log --since="$RELEASE_DATE" --oneline | wc -l)
+            echo "Commits since last $CHANNEL release: $COMMITS_SINCE"
+
+            # Require at least 2 commits for auto-release (nightly only)
+            # For manual beta/stable releases, always proceed
+            if [ "$CHANNEL" = "nightly" ]; then
+              if [ "$COMMITS_SINCE" -ge 2 ]; then
+                echo "has_commits=true" >> $GITHUB_OUTPUT
+              else
+                echo "has_commits=false" >> $GITHUB_OUTPUT
+                echo "Only $COMMITS_SINCE commit(s) found. Need at least 2 commits for auto-release."
+              fi
+            else
+              # Manual releases (beta/stable) always proceed
+              echo "has_commits=true" >> $GITHUB_OUTPUT
+            fi
+          fi
+        env:
+          GH_TOKEN: ${{ github.token }}
+
+      - name: Get last stable release (for beta versioning)
+        id: last_stable
+        if: steps.set_channel.outputs.channel == 'beta' || steps.set_channel.outputs.channel == 'nightly'
+        run: |
+          # Get the latest stable release (no .dev or .b)
+          LATEST_STABLE=$(gh release list --exclude-drafts --limit 100 --json createdAt,tagName,isPrerelease --jq '.[] | select(.tagName | contains(".dev") | not) | select(.tagName | contains(".b") | not)' 2>/dev/null | jq -s '.[0]' || echo "")
+
+          if [ -z "$LATEST_STABLE" ] || [ "$LATEST_STABLE" == "null" ]; then
+            echo "No previous stable releases found"
+            echo "stable_tag=" >> $GITHUB_OUTPUT
+          else
+            STABLE_TAG=$(echo "$LATEST_STABLE" | jq -r '.tagName')
+            echo "Latest stable release: $STABLE_TAG"
+            echo "stable_tag=$STABLE_TAG" >> $GITHUB_OUTPUT
+          fi
+        env:
+          GH_TOKEN: ${{ github.token }}
+
+      - name: Calculate next version
+        id: next_version
+        if: steps.check_commits.outputs.has_commits == 'true'
+        run: |
+          LAST_TAG="${{ steps.check_commits.outputs.last_tag }}"
+          CHANNEL="${{ steps.set_channel.outputs.channel }}"
+
+          case "$CHANNEL" in
+            nightly)
+              # Nightly: format 1.2.3.devYYYYMMDDHH
+              # Always one minor version ahead of the last stable release
+              TODAY=$(date -u +%Y%m%d)
+              HOUR=$(date -u +%H)
+              LAST_STABLE_TAG="${{ steps.last_stable.outputs.stable_tag }}"
+
+              # Determine the base version (should be one minor version ahead of stable)
+              if [ -n "$LAST_STABLE_TAG" ]; then
+                STABLE_VERSION=$(echo "$LAST_STABLE_TAG" | sed 's/^v//')
+
+                if [[ "$STABLE_VERSION" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
+                  MAJOR="${BASH_REMATCH[1]}"
+                  MINOR="${BASH_REMATCH[2]}"
+                  NEXT_MINOR=$((MINOR + 1))
+                  BASE_VERSION="${MAJOR}.${NEXT_MINOR}.0"
+                else
+                  BASE_VERSION="0.1.0"
+                fi
+              else
+                # No stable release found, start with default
+                BASE_VERSION="0.1.0"
+              fi
+
+              NEW_VERSION="${BASE_VERSION}.dev${TODAY}${HOUR}"
+              echo "Nightly version based on stable ${LAST_STABLE_TAG}: ${NEW_VERSION}"
+              ;;
+
+            beta)
+              # Beta: format 1.2.0b1, 1.2.0b2, etc.
+              # Always base the version on the last STABLE release, not dev versions
+              LAST_BETA_TAG="${{ steps.check_commits.outputs.last_tag }}"
+              LAST_STABLE_TAG="${{ steps.last_stable.outputs.stable_tag }}"
+
+              # Check if there's an existing beta version
+              if [ -n "$LAST_BETA_TAG" ]; then
+                BETA_VERSION=$(echo "$LAST_BETA_TAG" | sed 's/^v//')
+
+                # Check if it's already a beta version (e.g., 2.7.0b1)
+                if [[ "$BETA_VERSION" =~ ^([0-9]+\.[0-9]+\.[0-9]+)b([0-9]+)$ ]]; then
+                  BASE_VERSION="${BASH_REMATCH[1]}"
+                  BETA_NUM="${BASH_REMATCH[2]}"
+                  NEXT_BETA=$((BETA_NUM + 1))
+                  NEW_VERSION="${BASE_VERSION}b${NEXT_BETA}"
+                  echo "Incrementing existing beta: ${LAST_BETA_TAG} -> ${NEW_VERSION}"
+                else
+                  # Should not happen, but fallback
+                  NEW_VERSION="0.1.0b1"
+                fi
+              elif [ -n "$LAST_STABLE_TAG" ]; then
+                # No beta exists, increment minor from last stable and start at b1
+                STABLE_VERSION=$(echo "$LAST_STABLE_TAG" | sed 's/^v//')
+
+                if [[ "$STABLE_VERSION" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
+                  MAJOR="${BASH_REMATCH[1]}"
+                  MINOR="${BASH_REMATCH[2]}"
+                  NEXT_MINOR=$((MINOR + 1))
+                  NEW_VERSION="${MAJOR}.${NEXT_MINOR}.0b1"
+                  echo "Creating first beta based on stable ${LAST_STABLE_TAG}: ${NEW_VERSION}"
+                else
+                  NEW_VERSION="0.1.0b1"
+                fi
+              else
+                # No stable or beta found, start fresh
+                NEW_VERSION="0.1.0b1"
+              fi
+              ;;
+
+            stable)
+              # Stable: format 1.2.3, increment patch version
+              if [ -z "$LAST_TAG" ]; then
+                NEW_VERSION="0.1.0"
+              else
+                VERSION=$(echo "$LAST_TAG" | sed 's/^v//')
+
+                # Extract major.minor.patch and increment patch
+                if [[ "$VERSION" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
+                  MAJOR="${BASH_REMATCH[1]}"
+                  MINOR="${BASH_REMATCH[2]}"
+                  PATCH="${BASH_REMATCH[3]}"
+                  NEXT_PATCH=$((PATCH + 1))
+                  NEW_VERSION="${MAJOR}.${MINOR}.${NEXT_PATCH}"
+                else
+                  NEW_VERSION="0.1.0"
+                fi
+              fi
+              ;;
+          esac
+
+          echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT
+          echo "New $CHANNEL version: $NEW_VERSION"
+
+      - name: Log release decision
+        run: |
+          CHANNEL="${{ steps.set_channel.outputs.channel }}"
+          if [ "${{ steps.check_commits.outputs.has_commits }}" == "true" ]; then
+            echo "✅ Will create $CHANNEL release ${{ steps.next_version.outputs.version }}"
+          else
+            echo "⏭️ Skipping release - not enough commits"
+          fi
+
+  trigger-release:
+    name: Trigger Release Workflow
+    needs: check-and-release
+    if: needs.check-and-release.outputs.should_release == 'true'
+    permissions:
+      contents: write
+      pull-requests: read
+      packages: write
+      id-token: write # Required for PyPI publishing
+    uses: ./.github/workflows/release.yml
+    with:
+      version: ${{ needs.check-and-release.outputs.version }}
+      channel: ${{ needs.check-and-release.outputs.channel }}
+    secrets:
+      PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
+      PRIVILEGED_GITHUB_TOKEN: ${{ secrets.PRIVILEGED_GITHUB_TOKEN }}