Skip to content

Release

Release #72

name: Release
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
publish:
description: 'Upload the build to PyPI (true/false).'
required: false
default: 'false'
jobs:
build-and-verify:
runs-on: ubuntu-22.04
env:
PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Determine Version
id: version
run: |
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
version="${GITHUB_REF_NAME#v}"
tag="${GITHUB_REF_NAME}"
else
version=$(awk -F '"' '/^version = "/ {print $2; exit}' pyproject.toml)
tag="v${version}"
fi
echo "version=${version}" >> "$GITHUB_OUTPUT"
echo "tag=${tag}" >> "$GITHUB_OUTPUT"
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install project with test dependencies
run: |
python -m pip install --upgrade pip
pip install -e '.[test]'
- name: Run tests
run: pytest -v
- name: Install build tooling
run: python -m pip install --upgrade build twine
- name: Build distribution
run: python -m build
- name: Upload distribution artifact
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
- name: Decide whether to publish
id: intent
env:
EVENT_NAME: ${{ github.event_name }}
REF: ${{ github.ref }}
DISPATCH_PUBLISH: ${{ inputs.publish }}
run: |
publish=false
if [[ "$EVENT_NAME" == "push" && "$REF" == refs/tags/v* ]]; then
publish=true
elif [[ "$EVENT_NAME" == "workflow_dispatch" && "$DISPATCH_PUBLISH" == "true" ]]; then
publish=true
fi
echo "should_publish=$publish" >> "$GITHUB_OUTPUT"
- name: Publish to PyPI
id: publish
if: steps.intent.outputs.should_publish == 'true' && env.PYPI_API_TOKEN != ''
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
run: twine upload dist/*
- name: Get Changelog
id: changelog
if: steps.publish.conclusion == 'success'
run: |
TAG="${{ steps.version.outputs.tag }}"
if [[ ! -f CHANGELOG.md ]]; then
printf 'changelog=No changelog available\n' >> "$GITHUB_OUTPUT"
exit 0
fi
# Match heading with optional date suffix: ## v1.14.3 (2025-10-18) or ## v1.14.3
heading_pattern="^## ${TAG}( [(]|$)"
content=$(awk -v pattern="$heading_pattern" '
$0 ~ pattern {flag=1; next}
/^## / && flag {exit}
flag {print}
' CHANGELOG.md)
if [[ -z "$content" ]]; then
content="No changelog available"
fi
# Deduplicate blank lines
cleaned=$(printf '%s\n' "$content" | awk 'NF {print; blank=0; next} !blank {print; blank=1}')
{
printf 'changelog<<EOF\n'
printf '%s\n' "$cleaned"
printf 'EOF\n'
} >> "$GITHUB_OUTPUT"
- name: Create GitHub Release
if: steps.publish.conclusion == 'success' && success()
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ steps.version.outputs.tag }}
CHANGELOG_CONTENT: ${{ steps.changelog.outputs.changelog }}
run: |
# Create release with changelog
gh release create "$TAG" \
--title "Release $TAG" \
--notes "$CHANGELOG_CONTENT" \
dist/*
- name: Send Webhook Notification
if: steps.publish.conclusion == 'success' && success()
uses: distributhor/workflow-webhook@v3
with:
webhook_url: ${{ secrets.WEBHOOK_URL }}
data: |
{
"package": "cryptoservice",
"version": "${{ steps.version.outputs.version }}",
"commit": "${{ github.sha }}",
"author": "${{ github.actor }}",
"message": "Package published successfully to PyPI",
"changelog": ${{ steps.changelog.outputs.changelog != '' && toJSON(steps.changelog.outputs.changelog) || '"No changelog"' }},
"features": {
"new": ${{ contains(steps.changelog.outputs.changelog, '### Feature') }},
"fixes": ${{ contains(steps.changelog.outputs.changelog, '### Fix') }},
"breaking": ${{ contains(steps.changelog.outputs.changelog, 'BREAKING CHANGE') }}
},
"repository": "${{ github.repository }}",
"release_url": "https://github.com/${{ github.repository }}/releases/tag/${{ steps.version.outputs.tag }}"
}