Skip to content

Publish to PyPI / Github #9

Publish to PyPI / Github

Publish to PyPI / Github #9

name: Publish to PyPI / Github
on:
push:
tags:
- "v*.*.*"
workflow_dispatch:
inputs:
test_only:
description: 'Only publish to TestPyPI (for testing)'
required: false
default: false
type: boolean
version:
description: 'Version to publish (e.g., 0.1.0)'
required: true
type: string
jobs:
build-and-publish-pypi:
runs-on: ubuntu-latest
permissions:
contents: write
actions: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install uv
run: pip install uv
- name: Extract version
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
VERSION="${{ inputs.version }}"
else
VERSION=$(echo $GITHUB_REF | sed -n 's/refs\/tags\/v//p')
fi
echo "VERSION=$VERSION" >> $GITHUB_ENV
- name: Process README for PyPI
run: |
echo "📝 Processing README for PyPI compatibility..."
# Create a backup of original README
cp README.md README.original.md
# Process README for PyPI
python3 << 'EOF'
import re
# Read the original README
with open('README.md', 'r', encoding='utf-8') as f:
content = f.read()
print("🔄 Processing README content...")
# Convert relative image paths to absolute GitHub URLs
repo_url = "https://github.com/little1d/SpectrumLab/raw/main"
content = re.sub(
r'!\[([^\]]*)\]\((?!https?://)([^)]+)\)',
f'![\\1]({repo_url}/\\2)',
content
)
# Convert relative links to absolute GitHub URLs
content = re.sub(
r'\[([^\]]+)\]\((?!https?://)(?!#)([^)]+)\)',
f'[\\1](https://github.com/little1d/SpectrumLab/blob/main/\\2)',
content
)
# Remove GitHub-specific badges that might not render well on PyPI
# Keep essential badges but remove workflow status badges
content = re.sub(
r'!\[.*?\]\(https://github\.com/[^/]+/[^/]+/workflows/.*?\)',
'',
content
)
# Add PyPI-specific header if needed
pypi_header = "<!-- This README is automatically synced from GitHub repository -->\n\n"
# Only add header if it's not already there
if not content.startswith("<!-- This README is automatically synced"):
content = pypi_header + content
# Write the processed README
with open('README.md', 'w', encoding='utf-8') as f:
f.write(content)
print("✅ README processed successfully for PyPI")
EOF
- name: Update version in pyproject.toml
run: |
echo "📝 Updating version to $VERSION in pyproject.toml"
sed -i "s/version = \".*\"/version = \"$VERSION\"/" pyproject.toml
- name: Validate pyproject.toml
run: |
echo "🔍 Validating pyproject.toml..."
python3 -c "
import tomllib
with open('pyproject.toml', 'rb') as f:
data = tomllib.load(f)
print('✅ pyproject.toml is valid')
print(f'📦 Package: {data[\"project\"][\"name\"]}')
print(f'🏷️ Version: {data[\"project\"][\"version\"]}')
print(f'📄 README: {data[\"project\"].get(\"readme\", \"Not specified\")}')
"
- name: Build package
run: |
echo "🔨 Building package..."
uv build
echo "📦 Package built successfully!"
ls -la dist/
- name: Verify package contents
run: |
echo "🔍 Verifying package contents..."
pip install twine
twine check dist/*
echo "✅ Package verification completed!"
- name: Publish to TestPyPI
if: github.event_name == 'workflow_dispatch' && inputs.test_only
env:
UV_PUBLISH_TOKEN: ${{ secrets.TEST_PYPI_API_KEY }}
run: |
if [ -z "$UV_PUBLISH_TOKEN" ]; then
echo "❌ TEST_PYPI_API_KEY secret not found"
exit 1
fi
echo "🚀 Publishing to TestPyPI..."
uv publish --publish-url https://test.pypi.org/legacy/
echo "✅ Published to TestPyPI successfully!"
- name: Publish to PyPI
if: |
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')) ||
(github.event_name == 'workflow_dispatch' && !inputs.test_only)
env:
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_KEY }}
run: |
if [ -z "$UV_PUBLISH_TOKEN" ]; then
echo "❌ PYPI_API_KEY secret not found"
exit 1
fi
echo "🚀 Publishing to PyPI..."
uv publish --publish-url https://upload.pypi.org/legacy/
echo "✅ Published to PyPI successfully!"
- name: Restore original README
if: always()
run: |
echo "🔄 Restoring original README..."
if [ -f README.original.md ]; then
mv README.original.md README.md
echo "✅ Original README restored"
fi
- name: Upload built artifacts
uses: actions/upload-artifact@v4
with:
name: dist
path: dist
create-release:
name: Create GitHub Release
runs-on: ubuntu-latest
needs: build-and-publish-pypi
if: |
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')) ||
(github.event_name == 'workflow_dispatch' && !inputs.test_only)
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download built artifacts
uses: actions/download-artifact@v4
with:
name: dist
path: dist
- name: Check tag for pre-release
id: prerelease_check
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
# 手动触发时,根据版本号判断是否为预发布版本
if [[ "${{ inputs.version }}" == *"alpha"* ]] || [[ "${{ inputs.version }}" == *"beta"* ]] || [[ "${{ inputs.version }}" == *"rc"* ]]; then
echo "prerelease=true" >> $GITHUB_OUTPUT
else
echo "prerelease=false" >> $GITHUB_OUTPUT
fi
else
# Git 标签触发时,根据标签名判断
if [[ "${{ github.ref_name }}" == *"alpha"* ]] || [[ "${{ github.ref_name }}" == *"beta"* ]] || [[ "${{ github.ref_name }}" == *"rc"* ]]; then
echo "prerelease=true" >> $GITHUB_OUTPUT
else
echo "prerelease=false" >> $GITHUB_OUTPUT
fi
fi
- name: Generate release notes
id: release_notes
run: |
echo "📝 Generating release notes..."
cat > release_notes.md << 'EOF'
## 🚀 SpectrumLab Release
This release includes the latest updates to SpectrumLab, a pioneering unified platform for spectroscopy research.
### 📦 Installation
```bash
pip install spectrumlab
```
### 📚 Documentation
- [GitHub Repository](https://github.com/little1d/SpectrumLab)
- [PyPI Package](https://pypi.org/project/spectrumlab/)
### 🔗 Links
- **Source Code**: [GitHub](https://github.com/little1d/SpectrumLab)
- **Documentation**: [README](https://github.com/little1d/SpectrumLab/blob/main/README.md)
- **Issues**: [GitHub Issues](https://github.com/little1d/SpectrumLab/issues)
EOF
echo "✅ Release notes generated"
- name: Release to GitHub
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.event_name == 'workflow_dispatch' && format('v{0}', inputs.version) || github.ref_name }}
body_path: release_notes.md
draft: false
prerelease: ${{ steps.prerelease_check.outputs.prerelease }}
files: dist/*
sync-readme-status:
name: README Sync Status
runs-on: ubuntu-latest
needs: build-and-publish-pypi
if: always()
steps:
- name: README Sync Summary
run: |
echo "📋 README Sync Summary"
echo "===================="
echo "✅ GitHub README: Always stays as-is"
echo "🔄 PyPI README: Automatically processed and synced during package build"
echo "📦 Package Status: ${{ needs.build-and-publish-pypi.result }}"
if [ "${{ needs.build-and-publish-pypi.result }}" = "success" ]; then
echo "🎉 README successfully synced to PyPI!"
else
echo "❌ README sync failed - check build logs"
fi