Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
291 changes: 291 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
name: CI/CD Pipeline

on:
push:
branches:
- main
pull_request:
types: [opened, synchronize, reopened]
workflow_dispatch:
inputs:
bump_type:
description: 'Version bump type'
required: true
type: choice
options:
- patch
- minor
- major
description:
description: 'Release description (optional)'
required: false
type: string

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
# REQUIRED CI CHECKS - All must pass before release
# These jobs ensure code quality and tests pass before any release

# Linting and formatting
lint:
name: Lint and Format Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.13'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"

- name: Run Ruff linting
run: ruff check .

- name: Check Ruff formatting
run: ruff format --check .

- name: Run mypy
run: mypy src

- name: Check file size limit
run: python scripts/check_file_size.py

# Test on latest Python version only
test:
name: Test (Python 3.13)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.13'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"

- name: Run tests
run: pytest tests/ -v --cov=src --cov-report=xml --cov-report=term

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
file: ./coverage.xml
fail_ci_if_error: false

# Build package - only runs if lint and test pass
build:
name: Build Package
runs-on: ubuntu-latest
needs: [lint, test]
steps:
- uses: actions/checkout@v4

- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.13'

- name: Install build dependencies
run: |
python -m pip install --upgrade pip
pip install build twine

- name: Build package
run: python -m build

- name: Check package
run: twine check dist/*

- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/

# Check for changelog fragments in PRs (similar to changesets check)
changelog:
name: Changelog Fragment Check
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.13"

- name: Install scriv
run: pip install "scriv[toml]"

- name: Check for changelog fragments
run: |
# Get list of fragment files (excluding README and template)
FRAGMENTS=$(find changelog.d -name "*.md" ! -name "README.md" ! -name "*.j2" 2>/dev/null | wc -l)

# Get changed files in PR
CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD)

# Check if any source files changed (excluding docs and config)
SOURCE_CHANGED=$(echo "$CHANGED_FILES" | grep -E "^(src/|tests/|scripts/)" | wc -l)

if [ "$SOURCE_CHANGED" -gt 0 ] && [ "$FRAGMENTS" -eq 0 ]; then
echo "::warning::No changelog fragment found. Please run 'scriv create' and document your changes."
echo ""
echo "To create a changelog fragment:"
echo " pip install 'scriv[toml]'"
echo " scriv create"
echo ""
echo "This is similar to adding a changeset in JavaScript projects."
echo "See changelog.d/README.md for more information."
# Note: This is a warning, not a failure, to allow flexibility
# Change 'exit 0' to 'exit 1' to make it required
exit 0
fi

echo "βœ“ Changelog check passed"

# RELEASE JOBS - Only run after all CI checks pass

# Automatic release on push to main (if version changed)
auto-release:
name: Auto Release
needs: [lint, test, build]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.13'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install build twine

- name: Check if version changed
id: version_check
run: |
# Get current version from pyproject.toml
CURRENT_VERSION=$(grep -Po '(?<=^version = ")[^"]*' pyproject.toml)
echo "current_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT

# Check if tag exists
if git rev-parse "v$CURRENT_VERSION" >/dev/null 2>&1; then
echo "Tag v$CURRENT_VERSION already exists, skipping release"
echo "should_release=false" >> $GITHUB_OUTPUT
else
echo "New version detected: $CURRENT_VERSION"
echo "should_release=true" >> $GITHUB_OUTPUT
fi

- name: Download artifacts
if: steps.version_check.outputs.should_release == 'true'
uses: actions/download-artifact@v4
with:
name: dist
path: dist/

- name: Publish to PyPI
if: steps.version_check.outputs.should_release == 'true'
uses: pypa/gh-action-pypi-publish@release/v1

- name: Create GitHub Release
if: steps.version_check.outputs.should_release == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
python scripts/create_github_release.py \
--version "${{ steps.version_check.outputs.current_version }}" \
--repository "${{ github.repository }}"

# Manual release via workflow_dispatch - only after CI passes
manual-release:
name: Manual Release
needs: [lint, test, build]
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}

- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.13'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install build twine "scriv[toml]"

- name: Configure git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

- name: Collect changelog fragments
run: |
# Check if there are any fragments to collect
FRAGMENTS=$(find changelog.d -name "*.md" ! -name "README.md" ! -name "*.j2" 2>/dev/null | wc -l)
if [ "$FRAGMENTS" -gt 0 ]; then
echo "Found $FRAGMENTS changelog fragment(s), collecting..."
scriv collect --version "${{ github.event.inputs.bump_type }}"
else
echo "No changelog fragments found, skipping collection"
fi

- name: Version and commit
id: version
run: |
python scripts/version_and_commit.py \
--bump-type "${{ github.event.inputs.bump_type }}" \
--description "${{ github.event.inputs.description }}"

- name: Build package
if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true'
run: python -m build

- name: Check package
if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true'
run: twine check dist/*

- name: Publish to PyPI
if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true'
uses: pypa/gh-action-pypi-publish@release/v1

- name: Create GitHub Release
if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
python scripts/create_github_release.py \
--version "${{ steps.version.outputs.new_version }}" \
--repository "${{ github.repository }}"
25 changes: 25 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- id: check-merge-conflict
- id: check-toml
- id: debug-statements

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.4
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- id: ruff-format

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.13.0
hooks:
- id: mypy
additional_dependencies: [pytest, pytest-asyncio]
args: [--strict, --ignore-missing-imports]
5 changes: 5 additions & 0 deletions .ruff.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Ruff configuration
# This file provides additional settings beyond pyproject.toml

[lint.isort]
known-first-party = ["my_package"]
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

<!-- scriv-insert-here -->

## [0.1.0] - 2025-01-XX

### Added

- Initial project structure
- Basic example functions (add, multiply, delay)
- Comprehensive test suite with pytest
- Code quality tools (ruff, mypy)
- Pre-commit hooks configuration
- GitHub Actions CI/CD pipeline
- Scriv for changelog management (similar to Changesets)
- Release automation (PyPI + GitHub releases)
- Template structure for AI-driven Python development
Loading