+++ /dev/null
-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 }}
--- /dev/null
+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 }}