Scheduled Release #10
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 --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 <action@github.com>" | |
| 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" |