name: Scheduled Release on: schedule: # Every 2 weeks on Monday at 9 AM UTC - cron: '0 9 * * 1/2' workflow_dispatch: # Allow manual trigger inputs: skip_tests: description: 'Skip LLM tests (use for testing workflow)' required: false default: false type: boolean dry_run: description: 'Dry run - dont push changes or create release' required: false default: false type: boolean jobs: test-and-release: runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' steps: - uses: actions/checkout@v4 with: fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} - name: Setup UV uses: astral-sh/setup-uv@v3 - name: Install dependencies run: | uv sync --all-extras --dev - name: Run linting run: | uv run ruff check instructor examples tests - name: Run type checking run: | uv run pyright - name: Run core tests (no LLM) run: | uv run pytest tests/ -k "not openai and not llm and not anthropic and not gemini and not cohere and not mistral and not groq and not vertexai and not xai and not cerebras and not fireworks and not writer and not bedrock and not perplexity and not genai" --tb=short -v --maxfail=10 # Optional: Run LLM tests if you have API keys in secrets - name: Run LLM tests if: github.event.inputs.skip_tests != 'true' env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} COHERE_API_KEY: ${{ secrets.COHERE_API_KEY }} GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }} MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }} run: | echo "Running basic LLM tests if API keys are available..." # Run a subset of LLM tests to verify basic functionality if [ ! -z "$OPENAI_API_KEY" ]; then echo "Testing OpenAI integration..." uv run pytest tests/llm/test_openai/test_basics.py --tb=short -v --maxfail=1 || echo "OpenAI tests failed" fi if [ ! -z "$ANTHROPIC_API_KEY" ]; then echo "Testing Anthropic integration..." uv run pytest tests/llm/test_anthropic/test_basics.py --tb=short -v --maxfail=1 || echo "Anthropic tests failed" fi echo "LLM tests completed (non-blocking)" - name: Check for changes since last release id: changes run: | LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") if [ -z "$LAST_TAG" ]; then echo "has_changes=true" >> $GITHUB_OUTPUT echo "last_tag=none" >> $GITHUB_OUTPUT echo "change_count=initial" >> $GITHUB_OUTPUT else CHANGES=$(git rev-list $LAST_TAG..HEAD --count) echo "has_changes=$([[ $CHANGES -gt 0 ]] && echo true || echo false)" >> $GITHUB_OUTPUT echo "change_count=$CHANGES" >> $GITHUB_OUTPUT echo "last_tag=$LAST_TAG" >> $GITHUB_OUTPUT fi echo "Last tag: $LAST_TAG" echo "Changes since last tag: $(git rev-list $LAST_TAG..HEAD --count 2>/dev/null || echo 'N/A')" # Only proceed with release if tests passed AND there are changes - name: Get current version if: steps.changes.outputs.has_changes == 'true' id: current_version run: | VERSION=$(uv run python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])") echo "version=$VERSION" >> $GITHUB_OUTPUT echo "Current version: $VERSION" - name: Determine version bump type if: steps.changes.outputs.has_changes == 'true' id: version_type run: | # Check commit messages since last tag to determine bump type LAST_TAG="${{ steps.changes.outputs.last_tag }}" if [ "$LAST_TAG" = "none" ]; then COMMITS=$(git log --oneline HEAD~20..HEAD) else COMMITS=$(git log --oneline $LAST_TAG..HEAD) fi echo "Recent commits:" echo "$COMMITS" # Look for breaking changes or major features if echo "$COMMITS" | grep -qE "(BREAKING|feat!|fix!)"; then echo "bump_type=minor" >> $GITHUB_OUTPUT echo "Detected breaking changes - using minor bump" elif echo "$COMMITS" | grep -qE "feat:"; then echo "bump_type=minor" >> $GITHUB_OUTPUT echo "Detected new features - using minor bump" else echo "bump_type=patch" >> $GITHUB_OUTPUT echo "Using patch bump for bug fixes and chores" fi - name: Bump version if: steps.changes.outputs.has_changes == 'true' id: bump_version run: | CURRENT="${{ steps.current_version.outputs.version }}" BUMP_TYPE="${{ steps.version_type.outputs.bump_type }}" IFS='.' read -r major minor patch <<< "$CURRENT" case $BUMP_TYPE in major) major=$((major + 1)) minor=0 patch=0 ;; minor) minor=$((minor + 1)) patch=0 ;; patch) patch=$((patch + 1)) ;; esac NEW_VERSION="$major.$minor.$patch" echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT echo "Bumping from $CURRENT to $NEW_VERSION ($BUMP_TYPE)" # Update pyproject.toml sed -i "s/version = \"$CURRENT\"/version = \"$NEW_VERSION\"/" pyproject.toml - name: Update lockfile if: steps.changes.outputs.has_changes == 'true' run: | uv lock # Run tests again after version bump to make sure nothing broke - name: Final test run if: steps.changes.outputs.has_changes == 'true' run: | uv sync uv run pytest tests/ -k "not openai and not llm and not anthropic and not gemini and not cohere and not mistral and not groq and not vertexai and not xai and not cerebras and not fireworks and not writer and not bedrock and not perplexity and not genai" --tb=short --maxfail=5 - name: Generate changelog if: steps.changes.outputs.has_changes == 'true' id: changelog run: | LAST_TAG="${{ steps.changes.outputs.last_tag }}" NEW_VERSION="${{ steps.bump_version.outputs.new_version }}" if [ "$LAST_TAG" = "none" ]; then CHANGELOG=$(git log --oneline HEAD~30..HEAD --pretty=format:"- %s" | head -20) else CHANGELOG=$(git log --oneline $LAST_TAG..HEAD --pretty=format:"- %s") fi # Save changelog to file for GitHub release cat > CHANGELOG.md << EOF ## ๐Ÿš€ What's Changed $CHANGELOG ## ๐Ÿ”— Links **Full Changelog**: https://github.com/${{ github.repository }}/compare/$LAST_TAG...v$NEW_VERSION --- ๐Ÿค– *This release was automatically generated every 2 weeks* EOF echo "changelog_file=CHANGELOG.md" >> $GITHUB_OUTPUT - name: Create release commit if: steps.changes.outputs.has_changes == 'true' run: | git config --local user.email "action@github.com" git config --local user.name "GitHub Action" git add pyproject.toml uv.lock git commit -m "chore: automated release v${{ steps.bump_version.outputs.new_version }} ๐Ÿค– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: GitHub Action " git tag "v${{ steps.bump_version.outputs.new_version }}" - name: Push changes if: steps.changes.outputs.has_changes == 'true' && github.event.inputs.dry_run != 'true' run: | git push origin main git push origin "v${{ steps.bump_version.outputs.new_version }}" - name: Create GitHub Release if: steps.changes.outputs.has_changes == 'true' && github.event.inputs.dry_run != 'true' uses: ncipollo/release-action@v1 with: tag: "v${{ steps.bump_version.outputs.new_version }}" name: "๐Ÿš€ Release v${{ steps.bump_version.outputs.new_version }}" bodyFile: "CHANGELOG.md" draft: false prerelease: false - name: Dry run summary if: steps.changes.outputs.has_changes == 'true' && github.event.inputs.dry_run == 'true' run: | echo "๐Ÿงช DRY RUN MODE - No changes pushed" echo "Would have released: v${{ steps.bump_version.outputs.new_version }}" cat CHANGELOG.md # Optional: Publish to PyPI (uncomment if you want automatic PyPI releases) # - name: Build and publish to PyPI # if: steps.changes.outputs.has_changes == 'true' && secrets.PYPI_TOKEN != '' # env: # PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} # run: | # uv build # uv publish --token $PYPI_TOKEN # Summary outputs - name: Summary if: always() run: | echo "## ๐Ÿ“Š Scheduled Release Summary" >> $GITHUB_STEP_SUMMARY echo "- **Branch**: ${{ github.ref }}" >> $GITHUB_STEP_SUMMARY echo "- **Has Changes**: ${{ steps.changes.outputs.has_changes }}" >> $GITHUB_STEP_SUMMARY echo "- **Change Count**: ${{ steps.changes.outputs.change_count }}" >> $GITHUB_STEP_SUMMARY if [ "${{ steps.changes.outputs.has_changes }}" = "true" ]; then echo "- **Version**: ${{ steps.current_version.outputs.version }} โ†’ ${{ steps.bump_version.outputs.new_version }}" >> $GITHUB_STEP_SUMMARY echo "- **Bump Type**: ${{ steps.version_type.outputs.bump_type }}" >> $GITHUB_STEP_SUMMARY echo "- **Status**: โœ… Released" >> $GITHUB_STEP_SUMMARY else echo "- **Status**: โญ๏ธ Skipped (no changes)" >> $GITHUB_STEP_SUMMARY fi - name: Notify on failure if: failure() run: | echo "โŒ Scheduled release failed - check the logs above" echo "Common issues:" echo "- Tests failed" echo "- Linting issues" echo "- Type checking errors" echo "- Git push permissions"