Skip to content

Scheduled Release

Scheduled Release #8

name: Scheduled Release
# Automated release pipeline for gptme.
# - Runs twice weekly (Mon + Thu at noon UTC) to create dev pre-releases
# - Can be manually triggered for dev, patch, or minor releases
# - Dev releases use `.devYYYYMMDD` suffix and are marked as pre-releases
# - All releases get structured changelogs via build_changelog.py
# (dev: since last tag, stable: since last stable release)
#
# Scripts shared with `make release` / `make release-dev`:
# - scripts/bump_version.sh — version bump, commit, tag
# - scripts/publish_release.sh — push, GH release, PyPI publish
on:
schedule:
# Twice weekly: Monday and Thursday at 12:00 UTC
- cron: '0 12 * * 1,4'
workflow_dispatch:
inputs:
release_type:
description: 'Release type'
required: true
default: 'dev'
type: choice
options:
- dev # Development pre-release (.devYYYYMMDD)
- patch # Stable patch release (x.y.Z+1)
- minor # Stable minor release (x.Y+1.0)
permissions:
contents: write
actions: read
concurrency:
group: scheduled-release
cancel-in-progress: false
jobs:
check:
name: Pre-flight checks
runs-on: ubuntu-latest
outputs:
should_release: ${{ steps.check.outputs.should_release }}
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Check for changes and CI status
id: check
env:
GH_TOKEN: ${{ github.token }}
run: |
# Find last stable release tag (no pre-release suffix)
LAST_STABLE=$(git tag --sort=-version:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -1)
# Find last tag of any kind
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
COMMITS_SINCE_TAG=0
if [ -n "$LAST_TAG" ]; then
COMMITS_SINCE_TAG=$(git rev-list "${LAST_TAG}..HEAD" --count)
fi
echo "last_stable=$LAST_STABLE" >> "$GITHUB_OUTPUT"
echo "last_tag=$LAST_TAG" >> "$GITHUB_OUTPUT"
echo "commits=$COMMITS_SINCE_TAG" >> "$GITHUB_OUTPUT"
echo "Last stable: $LAST_STABLE | Last tag: $LAST_TAG | Commits since tag: $COMMITS_SINCE_TAG"
# Skip if no new commits
if [ "$COMMITS_SINCE_TAG" -eq 0 ]; then
echo "No new commits since $LAST_TAG — skipping release."
echo "should_release=false" >> "$GITHUB_OUTPUT"
exit 0
fi
# Verify CI is green on HEAD
HEAD_SHA=$(git rev-parse HEAD)
echo "Checking CI status for $HEAD_SHA..."
# Check combined commit status (covers both status checks and check runs)
# Use check-runs API which covers GitHub Actions
CONCLUSION=$(gh api "repos/${{ github.repository }}/commits/${HEAD_SHA}/check-suites" \
--jq '[.check_suites[] | select(.app.slug == "github-actions")] | map(.conclusion) | unique | .[]' 2>/dev/null || echo "unknown")
echo "CI conclusions: $CONCLUSION"
# If any suite is still running or has failed, skip
if echo "$CONCLUSION" | grep -qE "failure|action_required|timed_out"; then
echo "CI has failures on HEAD — skipping release."
echo "should_release=false" >> "$GITHUB_OUTPUT"
exit 0
fi
if echo "$CONCLUSION" | grep -qE "null|pending"; then
echo "CI is still running on HEAD — skipping release."
echo "should_release=false" >> "$GITHUB_OUTPUT"
exit 0
fi
# Require explicit success — skip if unknown or empty (API failure)
if [ -z "$CONCLUSION" ] || [ "$CONCLUSION" = "unknown" ]; then
echo "CI status unavailable on HEAD — skipping release."
echo "should_release=false" >> "$GITHUB_OUTPUT"
exit 0
fi
if ! echo "$CONCLUSION" | grep -q "success"; then
echo "No successful CI checks found on HEAD — skipping release."
echo "should_release=false" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "should_release=true" >> "$GITHUB_OUTPUT"
release:
name: Create release
needs: check
if: needs.check.outputs.should_release == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
ref: master
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Configure git
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- name: Install poetry
run: pipx install poetry
- name: Bump version
id: version
run: |
TYPE="${{ github.event.inputs.release_type || 'dev' }}"
bash scripts/bump_version.sh --type "$TYPE"
- name: Generate changelog
run: |
# Download the shared changelog generator (same as `make dist/CHANGELOG.md`)
wget -q -O scripts/build_changelog.py \
https://raw.githubusercontent.com/ActivityWatch/activitywatch/master/scripts/build_changelog.py
chmod +x scripts/build_changelog.py
pip install -q requests
TAG="${{ steps.version.outputs.tag }}"
PRERELEASE="${{ steps.version.outputs.prerelease }}"
# Use appropriate range: dev→since last tag, stable→since last stable
if [ "$PRERELEASE" = "true" ]; then
SINCE="${{ steps.version.outputs.last_tag }}"
else
SINCE="${{ steps.version.outputs.last_stable }}"
fi
echo "Generating changelog: ${SINCE}...${TAG}"
python3 scripts/build_changelog.py \
--range "${SINCE}...${TAG}" \
--project-title gptme \
--org gptme \
--repo gptme \
--output /tmp/changelog.md \
--add-version-header
echo "--- Generated changelog preview ---"
head -50 /tmp/changelog.md
- name: Publish release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
run: |
bash scripts/publish_release.sh --publish-pypi --notes-file /tmp/changelog.md