diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1ce0bd9..8fff669 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -82,35 +82,17 @@ jobs: env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - version-check: - name: Version Consistency - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Install uv - uses: astral-sh/setup-uv@v4 - with: - version: "latest" - - - name: Set up Python - run: uv python install 3.13 - - - name: Check version consistency - run: uv run python scripts/check_version.py - all-checks: name: All Checks Passed runs-on: ubuntu-latest - needs: [lint, typecheck, test, version-check] + needs: [lint, typecheck, test] if: always() steps: - name: Check all jobs passed run: | if [[ "${{ needs.lint.result }}" != "success" ]] || \ [[ "${{ needs.typecheck.result }}" != "success" ]] || \ - [[ "${{ needs.test.result }}" != "success" ]] || \ - [[ "${{ needs.version-check.result }}" != "success" ]]; then + [[ "${{ needs.test.result }}" != "success" ]]; then echo "One or more jobs failed" exit 1 fi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..4dc9f7f --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,28 @@ +name: Release + +on: + push: + branches: [main] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref_name }} + cancel-in-progress: false + +jobs: + release: + name: Semantic Release + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Python Semantic Release + id: release + uses: python-semantic-release/python-semantic-release@v10.2.0 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/pyproject.toml b/pyproject.toml index ccc2b80..8039179 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -147,3 +147,19 @@ dev = [ "types-bleach>=6.3.0.20251115", "types-markdown>=3.10.0.20251106", ] + +[tool.semantic_release] +version_toml = ["pyproject.toml:project.version"] +version_variables = [ + "src/__init__.py:__version__", + "src/config.py:app_version", +] +tag_format = "v{version}" +commit_message = "chore(release): {version}" +branch = "main" + +[tool.semantic_release.changelog] +changelog_file = "CHANGELOG.md" + +[tool.semantic_release.remote] +type = "github" diff --git a/scripts/check_version.py b/scripts/check_version.py deleted file mode 100644 index d83fb1e..0000000 --- a/scripts/check_version.py +++ /dev/null @@ -1,98 +0,0 @@ -#!/usr/bin/env python3 -"""Validate version consistency between __init__.py and pyproject.toml. - -This script ensures the version in src/__init__.py matches -the version in pyproject.toml to prevent version drift. - -Exit codes: - 0: Versions match - 1: Version mismatch or error -""" - -from __future__ import annotations - -import re -import sys -import tomllib -from pathlib import Path - - -def get_init_version(init_file: Path) -> str | None: - """Extract version from __init__.py file. - - Args: - init_file: Path to __init__.py file. - - Returns: - Version string if found, None otherwise. - """ - content = init_file.read_text(encoding="utf-8") - match = re.search(r'^__version__\s*=\s*["\']([^"\']+)["\']', content, re.MULTILINE) - return match.group(1) if match else None - - -def get_pyproject_version(pyproject_file: Path) -> str | None: - """Extract version from pyproject.toml file. - - Args: - pyproject_file: Path to pyproject.toml file. - - Returns: - Version string if found, None otherwise. - """ - try: - content = pyproject_file.read_text(encoding="utf-8") - data = tomllib.loads(content) - return data.get("project", {}).get("version") - except (tomllib.TOMLDecodeError, KeyError): - return None - - -def main() -> int: - """Main validation logic. - - Returns: - Exit code (0 for success, 1 for failure). - """ - # Determine project root (script is in scripts/ directory) - project_root = Path(__file__).parent.parent - init_file = project_root / "src" / "__init__.py" - pyproject_file = project_root / "pyproject.toml" - - # Check files exist - if not init_file.exists(): - print(f"❌ Error: {init_file} not found", file=sys.stderr) - return 1 - - if not pyproject_file.exists(): - print(f"❌ Error: {pyproject_file} not found", file=sys.stderr) - return 1 - - # Extract versions - init_version = get_init_version(init_file) - if init_version is None: - print(f"❌ Error: Could not find __version__ in {init_file}", file=sys.stderr) - return 1 - - pyproject_version = get_pyproject_version(pyproject_file) - if pyproject_version is None: - print(f"❌ Error: Could not find version in {pyproject_file}", file=sys.stderr) - return 1 - - # Compare versions - if init_version != pyproject_version: - print("❌ Version mismatch detected!", file=sys.stderr) - print(f" src/__init__.py: {init_version}", file=sys.stderr) - print(f" pyproject.toml: {pyproject_version}", file=sys.stderr) - print(file=sys.stderr) - print("Please update both files to match.", file=sys.stderr) - print("See RELEASING.md for version management guidelines.", file=sys.stderr) - return 1 - - # Success - print(f"✅ Version consistency check passed: {init_version}") - return 0 - - -if __name__ == "__main__": - sys.exit(main())