Skip to content

Commit 319a8a6

Browse files
committed
auto release for the win
1 parent 93a2302 commit 319a8a6

3 files changed

Lines changed: 108 additions & 32 deletions

File tree

.github/scripts/parse_changelog.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,19 @@
66
import sys
77

88

9+
def get_pyproject_version():
10+
"""Extract version from pyproject.toml."""
11+
try:
12+
with open("pyproject.toml") as f:
13+
content = f.read()
14+
match = re.search(r'^version\s*=\s*"([^"]+)"', content, re.MULTILINE)
15+
if match:
16+
return match.group(1)
17+
except FileNotFoundError:
18+
pass
19+
return None
20+
21+
922
def parse_changelog(filepath="CHANGELOG.md"):
1023
"""Extract the latest version and its content from CHANGELOG.md."""
1124
with open(filepath) as f:
@@ -24,6 +37,15 @@ def parse_changelog(filepath="CHANGELOG.md"):
2437
version = first_match.group(1)
2538
date = first_match.group(2)
2639

40+
# Validate version matches pyproject.toml
41+
pyproject_version = get_pyproject_version()
42+
if pyproject_version and pyproject_version != version:
43+
print(
44+
f"Error: Version mismatch! CHANGELOG.md has {version} but pyproject.toml has {pyproject_version}",
45+
file=sys.stderr,
46+
)
47+
sys.exit(1)
48+
2749
# Extract content between first and second version headers
2850
start_pos = first_match.end()
2951
if len(matches) > 1:
@@ -49,8 +71,8 @@ def parse_changelog(filepath="CHANGELOG.md"):
4971
f.write(f"version={version}\n")
5072
f.write(f"tag=v{version}\n")
5173

52-
print(f"Parsed version {version} (tag: v{version})")
53-
print(f"Release body: {len(body)} characters")
74+
print(f"✓ Version validated: {version} (tag: v{version})")
75+
print(f"Release body: {len(body)} characters")
5476

5577

5678
if __name__ == "__main__":

.github/workflows/auto-release.yaml

Lines changed: 84 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
with:
2020
fetch-depth: 0
2121

22-
- name: Parse Changelog
22+
- name: Parse Changelog and Validate Version
2323
id: changelog
2424
run: python3 .github/scripts/parse_changelog.py
2525

@@ -31,15 +31,37 @@ jobs:
3131
TAG="${{ steps.changelog.outputs.tag }}"
3232
3333
if gh release view "$TAG" &>/dev/null; then
34-
echo "Release $TAG already exists"
35-
echo "exists=true" >> $GITHUB_OUTPUT
34+
# Check if it's a draft
35+
IS_DRAFT=$(gh release view "$TAG" --json isDraft --jq '.isDraft')
36+
if [ "$IS_DRAFT" = "true" ]; then
37+
echo "Draft release $TAG exists, will retry publishing"
38+
echo "exists=false" >> $GITHUB_OUTPUT
39+
echo "is_draft=true" >> $GITHUB_OUTPUT
40+
else
41+
echo "Published release $TAG already exists"
42+
echo "exists=true" >> $GITHUB_OUTPUT
43+
echo "is_draft=false" >> $GITHUB_OUTPUT
44+
fi
3645
else
3746
echo "Release $TAG does not exist"
3847
echo "exists=false" >> $GITHUB_OUTPUT
48+
echo "is_draft=false" >> $GITHUB_OUTPUT
3949
fi
4050
41-
- name: Create Release
51+
- name: Run Tests
4252
if: steps.check_release.outputs.exists == 'false'
53+
uses: ./.github/actions/test
54+
55+
- name: Generate Test Badge
56+
if: steps.check_release.outputs.exists == 'false'
57+
run: |
58+
echo "" >> release_body.txt
59+
echo "---" >> release_body.txt
60+
echo "" >> release_body.txt
61+
echo "**Tests:** ✅ All tests passed" >> release_body.txt
62+
63+
- name: Create Draft Release
64+
if: steps.check_release.outputs.exists == 'false' && steps.check_release.outputs.is_draft == 'false'
4365
env:
4466
GH_TOKEN: ${{ github.token }}
4567
run: |
@@ -48,8 +70,66 @@ jobs:
4870
4971
gh release create "$TAG" \
5072
--title "Release $VERSION" \
73+
--notes-file release_body.txt \
74+
--draft
75+
76+
- name: Update Draft Release Notes
77+
if: steps.check_release.outputs.exists == 'false' && steps.check_release.outputs.is_draft == 'true'
78+
env:
79+
GH_TOKEN: ${{ github.token }}
80+
run: |
81+
TAG="${{ steps.changelog.outputs.tag }}"
82+
echo "Updating existing draft release $TAG"
83+
gh release edit "$TAG" --notes-file release_body.txt
84+
85+
- name: Publish to PyPI Test
86+
if: steps.check_release.outputs.exists == 'false'
87+
uses: ./.github/actions/publish
88+
with:
89+
PUBLISH_INDEX: test-pypi
90+
PYPI_TOKEN: ${{ secrets.PYPI_TEST_TOKEN }}
91+
92+
- name: Publish to PyPI
93+
if: steps.check_release.outputs.exists == 'false'
94+
uses: ./.github/actions/publish
95+
with:
96+
PUBLISH_INDEX: pypi
97+
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
98+
99+
- name: Publish Release
100+
if: steps.check_release.outputs.exists == 'false'
101+
env:
102+
GH_TOKEN: ${{ github.token }}
103+
run: |
104+
TAG="${{ steps.changelog.outputs.tag }}"
105+
106+
# Update release body to add PyPI link
107+
echo "" >> release_body.txt
108+
echo "**PyPI:** 📦 Published to [PyPI](https://pypi.org/project/pgmob/${{ steps.changelog.outputs.version }}/)" >> release_body.txt
109+
110+
# Publish the draft release
111+
gh release edit "$TAG" \
112+
--draft=false \
51113
--notes-file release_body.txt
52114
115+
- name: Release Published
116+
if: steps.check_release.outputs.exists == 'false'
117+
run: |
118+
echo "✓ Release ${{ steps.changelog.outputs.tag }} published successfully"
119+
echo "✓ Published to PyPI: https://pypi.org/project/pgmob/${{ steps.changelog.outputs.version }}/"
120+
121+
- name: Cleanup on Failure
122+
if: failure() && steps.check_release.outputs.exists == 'false'
123+
env:
124+
GH_TOKEN: ${{ github.token }}
125+
run: |
126+
TAG="${{ steps.changelog.outputs.tag }}"
127+
echo "⚠️ Workflow failed. Draft release $TAG remains unpublished."
128+
echo "⚠️ Fix the issue and push again to retry."
129+
echo "⚠️ Or manually delete the draft and tag to start fresh:"
130+
echo " gh release delete $TAG --yes"
131+
echo " git push --delete origin $TAG"
132+
53133
- name: Skip Release
54134
if: steps.check_release.outputs.exists == 'true'
55135
run: |

.github/workflows/release.yaml

Lines changed: 0 additions & 26 deletions
This file was deleted.

0 commit comments

Comments
 (0)