Skip to content

refactor(ci): use only one file for publishing to pypi #25

refactor(ci): use only one file for publishing to pypi

refactor(ci): use only one file for publishing to pypi #25

Workflow file for this run

name: Release to PyPI
on:
push:
branches: [main]
paths:
- "packages/**"
- ".github/workflows/release.yml"
workflow_dispatch:
inputs:
bump_type:
description: "Version bump type"
required: false
type: choice
default: "patch"
options:
- patch
- minor
- major
prerelease:
description: "Is this a pre-release? (e.g., b1, rc1) - leave empty for stable release"
required: false
type: string
release_type:
description: "Release type"
required: false
type: choice
default: "alpha"
options:
- alpha
- stable
jobs:
# Run all CI checks first
ci:
uses: ./.github/workflows/ci.yml
release:
name: ${{ github.event_name == 'push' && 'Alpha Release' || (github.event.inputs.release_type == 'alpha' && 'Alpha Release' || 'Stable Release') }}
needs: ci
runs-on: ubuntu-latest
permissions:
id-token: write # Required for PyPI trusted publishing
contents: write # Required for creating releases and pushing tags
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install uv
uses: astral-sh/setup-uv@v4
- name: Install commitizen
run: uv pip install --system commitizen
if: github.event_name == 'workflow_dispatch' && github.event.inputs.release_type == 'stable'
- name: Configure git
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
if: github.event_name == 'workflow_dispatch' && github.event.inputs.release_type == 'stable'
- name: Determine release mode
id: mode
run: |
if [ "${{ github.event_name }}" == "push" ]; then
echo "mode=alpha" >> $GITHUB_OUTPUT
echo "Auto-triggered alpha release"
elif [ "${{ github.event.inputs.release_type }}" == "alpha" ]; then
echo "mode=alpha" >> $GITHUB_OUTPUT
echo "Manual alpha release"
else
echo "mode=stable" >> $GITHUB_OUTPUT
echo "Manual stable release"
fi
- name: Calculate version
id: version
run: |
MODE="${{ steps.mode.outputs.mode }}"
if [ "$MODE" == "alpha" ]; then
# Alpha version calculation
TOTAL_COMMITS=$(git rev-list --count HEAD)
CURRENT_VERSION=$(grep -m 1 -A 5 '^\[project\]' pyproject.toml | grep 'version = ' | sed 's/version = "\(.*\)"/\1/')
BASE_VERSION=$(echo $CURRENT_VERSION | sed 's/a.*//')
VERSION="${BASE_VERSION}a${TOTAL_COMMITS}"
echo "Alpha version: $VERSION"
echo "is_prerelease=true" >> $GITHUB_OUTPUT
else
# Stable release with commitizen
if [ -n "${{ github.event.inputs.prerelease }}" ]; then
# Prerelease (e.g., 0.1.0b1, 0.1.0rc1)
cz bump --increment ${{ github.event.inputs.bump_type }} --yes --no-verify
NEW_VERSION=$(grep -m 1 -A 5 '^\[project\]' pyproject.toml | grep 'version = ' | sed 's/version = "\(.*\)"/\1/')
VERSION="${NEW_VERSION}${{ github.event.inputs.prerelease }}"
echo "Prerelease version: $VERSION"
echo "is_prerelease=true" >> $GITHUB_OUTPUT
else
# Stable release (e.g., 0.1.0, 1.0.0)
cz bump --increment ${{ github.event.inputs.bump_type }} --yes
VERSION=$(grep -m 1 -A 5 '^\[project\]' pyproject.toml | grep 'version = ' | sed 's/version = "\(.*\)"/\1/')
echo "Stable version: $VERSION"
echo "is_prerelease=false" >> $GITHUB_OUTPUT
fi
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Final version: $VERSION"
- name: Update version files
run: |
VERSION="${{ steps.version.outputs.version }}"
IS_PRERELEASE="${{ steps.version.outputs.is_prerelease }}"
python3 << 'PYTHON_SCRIPT'
import re
version = "${{ steps.version.outputs.version }}"
is_prerelease = "${{ steps.version.outputs.is_prerelease }}" == "true"
base_version = version.split('a')[0].split('b')[0].split('rc')[0]
# Update pyproject.toml files
toml_files = [
'pyproject.toml',
'packages/myfy-core/pyproject.toml',
'packages/myfy-web/pyproject.toml',
'packages/myfy-cli/pyproject.toml',
'packages/myfy-frontend/pyproject.toml',
'packages/myfy/pyproject.toml'
]
for file in toml_files:
with open(file, 'r') as f:
content = f.read()
content = re.sub(
r'(\[project\][^\[]*\nname = [^\n]+\n)version = "[^"]*"',
r'\1version = "' + version + '"',
content,
count=1
)
with open(file, 'w') as f:
f.write(content)
# Update version.py files
version_files = [
'packages/myfy-core/myfy/core/version.py',
'packages/myfy-web/myfy/web/version.py',
'packages/myfy-cli/myfy_cli/version.py',
'packages/myfy-frontend/myfy/frontend/version.py',
'packages/myfy/myfy/version.py'
]
for file in version_files:
with open(file, 'r') as f:
content = f.read()
content = re.sub(r'__version__ = "[^"]*"', f'__version__ = "{version}"', content)
with open(file, 'w') as f:
f.write(content)
# Update dependency constraints
dep_files = [
'packages/myfy-web/pyproject.toml',
'packages/myfy-cli/pyproject.toml',
'packages/myfy-frontend/pyproject.toml',
'packages/myfy/pyproject.toml'
]
# For prereleases (alpha/beta/rc), use exact version
# For stable releases, use compatible release
if is_prerelease:
constraint = f'=={version}'
else:
constraint = f'~={base_version}'
for file in dep_files:
with open(file, 'r') as f:
content = f.read()
content = re.sub(r'myfy-core[~=]=?[0-9a-z.]+', f'myfy-core{constraint}', content)
content = re.sub(r'myfy-web[~=]=?[0-9a-z.]+', f'myfy-web{constraint}', content)
content = re.sub(r'myfy-cli[~=]=?[0-9a-z.]+', f'myfy-cli{constraint}', content)
with open(file, 'w') as f:
f.write(content)
print(f"Version updated to {version}")
PYTHON_SCRIPT
- name: Commit and tag (stable releases only)
if: steps.mode.outputs.mode == 'stable'
run: |
VERSION="${{ steps.version.outputs.version }}"
git add .
git commit -m "bump: version to ${VERSION}" || true
git tag -a "v${VERSION}" -m "Release v${VERSION}" || true
git push origin main
git push origin "v${VERSION}"
- name: Build packages
run: |
# Build in dependency order using --package flag
echo "Building myfy-core..."
uv build --package myfy-core --out-dir packages/myfy-core/dist
echo "Building myfy-web..."
uv build --package myfy-web --out-dir packages/myfy-web/dist
echo "Building myfy-cli..."
uv build --package myfy-cli --out-dir packages/myfy-cli/dist
echo "Building myfy-frontend..."
uv build --package myfy-frontend --out-dir packages/myfy-frontend/dist
echo "Building myfy meta-package..."
uv build --package myfy --out-dir packages/myfy/dist
echo "All packages built successfully"
- name: List built packages
run: |
echo "myfy-core:"
ls -lh packages/myfy-core/dist/
echo -e "\nmyfy-web:"
ls -lh packages/myfy-web/dist/
echo -e "\nmyfy-cli:"
ls -lh packages/myfy-cli/dist/
echo -e "\nmyfy-frontend:"
ls -lh packages/myfy-frontend/dist/
echo -e "\nmyfy:"
ls -lh packages/myfy/dist/
- name: Generate release notes (stable releases only)
if: steps.mode.outputs.mode == 'stable'
id: release_notes
run: |
VERSION="${{ steps.version.outputs.version }}"
# Create release notes from git log
echo "## What's Changed" > release_notes.md
echo "" >> release_notes.md
# Get commits since last tag
PREVIOUS_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
if [ -n "$PREVIOUS_TAG" ]; then
git log ${PREVIOUS_TAG}..HEAD --pretty=format:"- %s (%h)" >> release_notes.md
else
echo "Initial release" >> release_notes.md
fi
echo "" >> release_notes.md
echo "## Packages" >> release_notes.md
echo "- myfy-core $VERSION" >> release_notes.md
echo "- myfy-web $VERSION" >> release_notes.md
echo "- myfy-cli $VERSION" >> release_notes.md
echo "- myfy-frontend $VERSION" >> release_notes.md
echo "- myfy $VERSION" >> release_notes.md
- name: Create GitHub Release (stable releases only)
if: steps.mode.outputs.mode == 'stable'
uses: softprops/action-gh-release@v1
with:
tag_name: v${{ steps.version.outputs.version }}
name: Release v${{ steps.version.outputs.version }}
body_path: release_notes.md
draft: false
prerelease: ${{ steps.version.outputs.is_prerelease }}
files: |
packages/myfy-core/dist/*
packages/myfy-web/dist/*
packages/myfy-cli/dist/*
packages/myfy-frontend/dist/*
packages/myfy/dist/*
- name: Publish myfy-core to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: packages/myfy-core/dist/
verbose: true
- name: Publish myfy-web to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: packages/myfy-web/dist/
verbose: true
- name: Publish myfy-cli to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: packages/myfy-cli/dist/
verbose: true
- name: Publish myfy-frontend to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: packages/myfy-frontend/dist/
verbose: true
- name: Publish myfy to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: packages/myfy/dist/
verbose: true
validate-release:
name: Validate Release on Python ${{ matrix.python-version }}
needs: release
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.12", "3.13"]
steps:
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Wait for PyPI propagation
run: sleep 60
- name: Install and validate all packages
run: |
pip install myfy[all] --no-cache-dir
python -c "
import myfy
import myfy.core
import myfy.web
import myfy_cli
import myfy.frontend
print(f'myfy version: {myfy.__version__}')
print(f'myfy-core version: {myfy.core.__version__}')
print(f'myfy-web version: {myfy.web.__version__}')
print(f'myfy-cli version: {myfy_cli.__version__}')
print(f'myfy-frontend version: {myfy.frontend.__version__}')
# Verify all versions match
versions = [myfy.__version__, myfy.core.__version__, myfy.web.__version__, myfy_cli.__version__, myfy.frontend.__version__]
assert len(set(versions)) == 1, f'Version mismatch: {versions}'
print('✓ All versions match')
"
- name: Test CLI
run: |
myfy --help
echo "✓ CLI works"