diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3fcf692..1468f2c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,18 +1,25 @@ -name: CI - Run Unit Tests in Docker with Coverage +name: CI, Test, and Tag-Based Release with Dev TestPyPI on: push: - branches: [ main ] + branches: + - main # Trigger for pushes to main + tags: + - 'v*' # Trigger for pushes of tags like v1.0, v2.3.4 + pull_request: - branches: [ main ] + branches: [ main ] # Trigger for PRs targeting main + types: [opened, synchronize, reopened] jobs: + # --- Test Job (Remains the same) --- test: runs-on: ubuntu-latest - steps: - name: Checkout code uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -21,22 +28,15 @@ jobs: uses: docker/build-push-action@v5 with: context: . - file: ./Dockerfile # Assuming Dockerfile is at the root - # Build the 'dev' target stage + file: ./Dockerfile target: dev - # Tag the image locally so subsequent steps can use it tags: my-test-image:latest - # IMPORTANT: Load the image into the local Docker daemon load: true - # Enable layer caching using GitHub Actions cache cache-from: type=gha cache-to: type=gha,mode=max - name: Run tests with dFBA extra run: | - # Runs coverage collection via 'uv run' using --with pytest-cov. - # Uses --parallel-mode for coverage data. - # Generates uniquely named JUnit report. docker run --rm \ -v "$(pwd):/app" \ -w /app \ @@ -45,23 +45,15 @@ jobs: - name: Run tests without dFBA extra run: | - # Runs coverage collection via 'uv run' using --with pytest-cov. - # Uses --parallel-mode for coverage data. - # Generates uniquely named JUnit report. docker run --rm \ -v "$(pwd):/app" \ -w /app \ my-test-image:latest \ uv run --with pytest --with pytest-cov coverage run --parallel-mode -m pytest ./tests/core --junitxml=test-report-core.xml - # --- The step below only runs if BOTH test steps above succeeded --- - - name: Combine and Report Coverage if: success() run: | - # Runs combine, report, and html generation in a single container - # Saves the console summary to /app/coverage_summary.txt within the container - # which maps to coverage_summary.txt on the runner host via the volume mount. docker run --rm \ -v "$(pwd):/app" \ -w /app \ @@ -74,30 +66,121 @@ jobs: echo "Generating HTML coverage report..." && \ uv run --with pytest --with pytest-cov coverage html -d htmlcov \ ' - # Also echo the summary to the main job log for easy viewing there echo "--- Coverage Summary ---" cat coverage_summary.txt echo "------------------------" - name: Format Coverage Comment Body - # Prepare the content for the comment in a file - # Only run on successful PR builds if: success() && github.event_name == 'pull_request' run: | echo '### :test_tube: Coverage Report' > coverage_comment.md echo '' >> coverage_comment.md echo '```text' >> coverage_comment.md cat coverage_summary.txt >> coverage_comment.md - echo '' >> coverage_comment.md # Ensure newline before closing fence + echo '' >> coverage_comment.md echo '```' >> coverage_comment.md - # Optional: Add a hidden HTML comment marker for extra safety/identification - # echo "" >> coverage_comment.md - + - name: Post or Update Coverage Comment - # Use the sticky comment action - # Only run on successful PR builds if: success() && github.event_name == 'pull_request' uses: marocchino/sticky-pull-request-comment@v2 with: - # Tell the action to read the comment body from the file we just created path: coverage_comment.md + + # --- Job: Publish DEV version to TestPyPI (Runs ONLY on tag pushes) --- + publish-testpypi: + name: Publish Dev Version to TestPyPI + needs: test + # Run ONLY when a tag is pushed, AFTER tests pass + if: success() + runs-on: ubuntu-latest + environment: + name: testpypi + url: https://test.pypi.org/pypi + permissions: + id-token: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.10' + + - name: Install build dependencies + run: python -m pip install build twine + + # --- Add step to generate dev suffix --- + - name: Generate development version suffix + id: version_suffix + # Using timestamp for uniqueness on TestPyPI for potentially repeated tag tests + run: echo "suffix=$(date +'%Y%m%d%H%M%S')" >> $GITHUB_OUTPUT + + # --- Add step to update version --- + - name: Update version in pyproject.toml for TestPyPI + run: | + # Add .dev suffix to the version line + # This assumes version = "X.Y.Z" format in pyproject.toml + VERSION_SUFFIX="${{ steps.version_suffix.outputs.suffix }}" + # Use temp file for sed compatibility + sed -i.bak "s/^\(version\s*=\s*\"\)\([^\"]*\)\"/\1\2.dev${VERSION_SUFFIX}\"/" pyproject.toml + rm pyproject.toml.bak # Remove backup + echo "Updated pyproject.toml with dev version suffix for TestPyPI build:" + grep "^version" pyproject.toml + + - name: Build package with dev version + run: | + echo "Building package for tag ${{ github.ref_name }} with dev suffix..." + python -m build + + - name: Publish package distributions to TestPyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ + # skip-existing: true # Recommended for dev builds + + # --- Job: Publish CLEAN version to PyPI (Runs ONLY on tag pushes, after TestPyPI) --- + publish-pypi: + name: Publish Clean Version to PyPI + needs: publish-testpypi # Depends on the TestPyPI dev publish job + # Run ONLY when a tag is pushed, AFTER TestPyPI publish succeeds + if: success() && startsWith(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/pypi + permissions: + id-token: write + + steps: + # --- Checkout code AGAIN to get the original version --- + - name: Checkout ORIGINAL code for tag + uses: actions/checkout@v4 + with: + # Make sure we're checking out the code corresponding to the tag + # This ensures we get the pyproject.toml WITHOUT the .dev suffix + ref: ${{ github.ref }} + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.10' + + - name: Install build dependencies + run: python -m pip install build twine + + - name: Build package with CLEAN version + run: | + echo "Building package for tag ${{ github.ref_name }} for PyPI release (using original version)..." + grep "^version" pyproject.toml + # Builds the package using the version defined in the tagged commit's source + # because we checked out the original code again. + python -m build + + - name: Publish package distributions to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + # Defaults to PyPI, uses trusted publishing via OIDC