1- name : CI - Run Unit Tests in Docker with Coverage
1+ name : CI, Test, and Tag-Based Release with Dev TestPyPI
22
33on :
44 push :
5- branches : [ main ]
5+ branches :
6+ - main # Trigger for pushes to main
7+ tags :
8+ - ' v*' # Trigger for pushes of tags like v1.0, v2.3.4
9+
610 pull_request :
7- branches : [ main ]
11+ branches : [ main ] # Trigger for PRs targeting main
12+ types : [opened, synchronize, reopened]
813
914jobs :
15+ # --- Test Job (Remains the same) ---
1016 test :
1117 runs-on : ubuntu-latest
12-
1318 steps :
1419 - name : Checkout code
1520 uses : actions/checkout@v4
21+ with :
22+ fetch-depth : 0
1623
1724 - name : Set up Docker Buildx
1825 uses : docker/setup-buildx-action@v3
@@ -21,22 +28,15 @@ jobs:
2128 uses : docker/build-push-action@v5
2229 with :
2330 context : .
24- file : ./Dockerfile # Assuming Dockerfile is at the root
25- # Build the 'dev' target stage
31+ file : ./Dockerfile
2632 target : dev
27- # Tag the image locally so subsequent steps can use it
2833 tags : my-test-image:latest
29- # IMPORTANT: Load the image into the local Docker daemon
3034 load : true
31- # Enable layer caching using GitHub Actions cache
3235 cache-from : type=gha
3336 cache-to : type=gha,mode=max
3437
3538 - name : Run tests with dFBA extra
3639 run : |
37- # Runs coverage collection via 'uv run' using --with pytest-cov.
38- # Uses --parallel-mode for coverage data.
39- # Generates uniquely named JUnit report.
4040 docker run --rm \
4141 -v "$(pwd):/app" \
4242 -w /app \
@@ -45,23 +45,15 @@ jobs:
4545
4646 - name : Run tests without dFBA extra
4747 run : |
48- # Runs coverage collection via 'uv run' using --with pytest-cov.
49- # Uses --parallel-mode for coverage data.
50- # Generates uniquely named JUnit report.
5148 docker run --rm \
5249 -v "$(pwd):/app" \
5350 -w /app \
5451 my-test-image:latest \
5552 uv run --with pytest --with pytest-cov coverage run --parallel-mode -m pytest ./tests/core --junitxml=test-report-core.xml
5653
57- # --- The step below only runs if BOTH test steps above succeeded ---
58-
5954 - name : Combine and Report Coverage
6055 if : success()
6156 run : |
62- # Runs combine, report, and html generation in a single container
63- # Saves the console summary to /app/coverage_summary.txt within the container
64- # which maps to coverage_summary.txt on the runner host via the volume mount.
6557 docker run --rm \
6658 -v "$(pwd):/app" \
6759 -w /app \
@@ -74,30 +66,121 @@ jobs:
7466 echo "Generating HTML coverage report..." && \
7567 uv run --with pytest --with pytest-cov coverage html -d htmlcov \
7668 '
77- # Also echo the summary to the main job log for easy viewing there
7869 echo "--- Coverage Summary ---"
7970 cat coverage_summary.txt
8071 echo "------------------------"
8172
8273 - name : Format Coverage Comment Body
83- # Prepare the content for the comment in a file
84- # Only run on successful PR builds
8574 if : success() && github.event_name == 'pull_request'
8675 run : |
8776 echo '### :test_tube: Coverage Report' > coverage_comment.md
8877 echo '' >> coverage_comment.md
8978 echo '```text' >> coverage_comment.md
9079 cat coverage_summary.txt >> coverage_comment.md
91- echo '' >> coverage_comment.md # Ensure newline before closing fence
80+ echo '' >> coverage_comment.md
9281 echo '```' >> coverage_comment.md
93- # Optional: Add a hidden HTML comment marker for extra safety/identification
94- # echo "<!-- coverage-report-marker -->" >> coverage_comment.md
95-
82+
9683 - name : Post or Update Coverage Comment
97- # Use the sticky comment action
98- # Only run on successful PR builds
9984 if : success() && github.event_name == 'pull_request'
10085 uses : marocchino/sticky-pull-request-comment@v2
10186 with :
102- # Tell the action to read the comment body from the file we just created
10387 path : coverage_comment.md
88+
89+ # --- Job: Publish DEV version to TestPyPI (Runs ONLY on tag pushes) ---
90+ publish-testpypi :
91+ name : Publish Dev Version to TestPyPI
92+ needs : test
93+ # Run ONLY when a tag is pushed, AFTER tests pass
94+ if : success()
95+ runs-on : ubuntu-latest
96+ environment :
97+ name : testpypi
98+ url : https://test.pypi.org/pypi
99+ permissions :
100+ id-token : write
101+
102+ steps :
103+ - name : Checkout code
104+ uses : actions/checkout@v4
105+ with :
106+ fetch-depth : 0
107+
108+ - name : Set up Python
109+ uses : actions/setup-python@v5
110+ with :
111+ python-version : ' 3.10'
112+
113+ - name : Install build dependencies
114+ run : python -m pip install build twine
115+
116+ # --- Add step to generate dev suffix ---
117+ - name : Generate development version suffix
118+ id : version_suffix
119+ # Using timestamp for uniqueness on TestPyPI for potentially repeated tag tests
120+ run : echo "suffix=$(date +'%Y%m%d%H%M%S')" >> $GITHUB_OUTPUT
121+
122+ # --- Add step to update version ---
123+ - name : Update version in pyproject.toml for TestPyPI
124+ run : |
125+ # Add .dev<timestamp> suffix to the version line
126+ # This assumes version = "X.Y.Z" format in pyproject.toml
127+ VERSION_SUFFIX="${{ steps.version_suffix.outputs.suffix }}"
128+ # Use temp file for sed compatibility
129+ sed -i.bak "s/^\(version\s*=\s*\"\)\([^\"]*\)\"/\1\2.dev${VERSION_SUFFIX}\"/" pyproject.toml
130+ rm pyproject.toml.bak # Remove backup
131+ echo "Updated pyproject.toml with dev version suffix for TestPyPI build:"
132+ grep "^version" pyproject.toml
133+
134+ - name : Build package with dev version
135+ run : |
136+ echo "Building package for tag ${{ github.ref_name }} with dev suffix..."
137+ python -m build
138+
139+ - name : Publish package distributions to TestPyPI
140+ uses : pypa/gh-action-pypi-publish@release/v1
141+ with :
142+ repository-url : https://test.pypi.org/legacy/
143+ # skip-existing: true # Recommended for dev builds
144+
145+ # --- Job: Publish CLEAN version to PyPI (Runs ONLY on tag pushes, after TestPyPI) ---
146+ publish-pypi :
147+ name : Publish Clean Version to PyPI
148+ needs : publish-testpypi # Depends on the TestPyPI dev publish job
149+ # Run ONLY when a tag is pushed, AFTER TestPyPI publish succeeds
150+ if : success() && startsWith(github.ref, 'refs/tags/')
151+ runs-on : ubuntu-latest
152+ environment :
153+ name : pypi
154+ url : https://pypi.org/pypi
155+ permissions :
156+ id-token : write
157+
158+ steps :
159+ # --- Checkout code AGAIN to get the original version ---
160+ - name : Checkout ORIGINAL code for tag
161+ uses : actions/checkout@v4
162+ with :
163+ # Make sure we're checking out the code corresponding to the tag
164+ # This ensures we get the pyproject.toml WITHOUT the .dev suffix
165+ ref : ${{ github.ref }}
166+ fetch-depth : 0
167+
168+ - name : Set up Python
169+ uses : actions/setup-python@v5
170+ with :
171+ python-version : ' 3.10'
172+
173+ - name : Install build dependencies
174+ run : python -m pip install build twine
175+
176+ - name : Build package with CLEAN version
177+ run : |
178+ echo "Building package for tag ${{ github.ref_name }} for PyPI release (using original version)..."
179+ grep "^version" pyproject.toml
180+ # Builds the package using the version defined in the tagged commit's source
181+ # because we checked out the original code again.
182+ python -m build
183+
184+ - name : Publish package distributions to PyPI
185+ uses : pypa/gh-action-pypi-publish@release/v1
186+ # Defaults to PyPI, uses trusted publishing via OIDC
0 commit comments