name: Release
on:
push:
tags:
- 'v*.*.*'
workflow_dispatch:
inputs:
version:
description: 'Release version (e.g., v2.0.0)'
required: true
type: string
prerelease:
description: 'Mark as pre-release'
required: false
type: boolean
default: false
env:
PYTHON_VERSION: '3.10'
jobs:
validate:
name: Validate Release
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
is_prerelease: ${{ steps.version.outputs.is_prerelease }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Extract version
id: version
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
VERSION="${{ github.event.inputs.version }}"
IS_PRERELEASE="${{ github.event.inputs.prerelease }}"
else
VERSION=${GITHUB_REF#refs/tags/}
# Check if version contains alpha, beta, rc
if [[ $VERSION =~ (alpha|beta|rc) ]]; then
IS_PRERELEASE=true
else
IS_PRERELEASE=false
fi
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "is_prerelease=$IS_PRERELEASE" >> $GITHUB_OUTPUT
echo "Release version: $VERSION"
echo "Is prerelease: $IS_PRERELEASE"
- name: Validate version format
run: |
VERSION="${{ steps.version.outputs.version }}"
if [[ ! $VERSION =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-(alpha|beta|rc)\.[0-9]+)?$ ]]; then
echo "Invalid version format: $VERSION"
echo "Expected format: vX.Y.Z or vX.Y.Z-alpha.N, vX.Y.Z-beta.N, vX.Y.Z-rc.N"
exit 1
fi
- name: Check if tag exists
if: github.event_name == 'workflow_dispatch'
run: |
VERSION="${{ steps.version.outputs.version }}"
if git rev-parse "$VERSION" >/dev/null 2>&1; then
echo "Tag $VERSION already exists"
exit 1
fi
test:
name: Run Tests
runs-on: ubuntu-latest
needs: validate
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: 'pip'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest pytest-cov
- name: Run tests
run: |
pytest test_server.py -v --cov=. --cov-report=term-missing
env:
PYTHONPATH: .
- name: Test server startup
run: |
timeout 10s python server.py --test || exit_code=$?
if [ $exit_code -eq 124 ] || [ $exit_code -eq 0 ]; then
echo "Server test passed"
else
echo "Server test failed"
exit 1
fi
build:
name: Build Release Assets
runs-on: ${{ matrix.os }}
needs: [validate, test]
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install build pyinstaller
- name: Build Python package
if: matrix.os == 'ubuntu-latest'
run: |
python -m build
- name: Build standalone executable
run: |
pyinstaller --onefile --name pddl-mcp-server-${{ runner.os }} server.py
- name: Create release archive
shell: bash
run: |
VERSION="${{ needs.validate.outputs.version }}"
OS_NAME=$(echo ${{ runner.os }} | tr '[:upper:]' '[:lower:]')
# Create release directory
mkdir -p release
# Copy main files
cp server.py release/
cp requirements.txt release/
cp README.md release/
cp LICENSE release/
cp .mcp.json release/
# Copy templates and examples
cp -r templates/ release/ 2>/dev/null || true
cp -r examples/ release/ 2>/dev/null || true
# Copy executable
if [ "${{ runner.os }}" = "Windows" ]; then
cp dist/pddl-mcp-server-Windows.exe release/
ARCHIVE_NAME="pddl-mcp-server-${VERSION}-${OS_NAME}.zip"
cd release && zip -r ../$ARCHIVE_NAME . && cd ..
else
cp dist/pddl-mcp-server-${{ runner.os }} release/
ARCHIVE_NAME="pddl-mcp-server-${VERSION}-${OS_NAME}.tar.gz"
tar -czf $ARCHIVE_NAME -C release .
fi
echo "ARCHIVE_NAME=$ARCHIVE_NAME" >> $GITHUB_ENV
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: release-${{ runner.os }}
path: |
${{ env.ARCHIVE_NAME }}
dist/
retention-days: 7
create_release:
name: Create GitHub Release
runs-on: ubuntu-latest
needs: [validate, test, build]
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Download all artifacts
uses: actions/download-artifact@v3
with:
path: artifacts
- name: Generate release notes
id: release_notes
run: |
VERSION="${{ needs.validate.outputs.version }}"
# Get previous tag
PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
# Generate changelog
echo "## What's Changed" > release_notes.md
echo "" >> release_notes.md
if [ -n "$PREV_TAG" ]; then
echo "### Commits since $PREV_TAG" >> release_notes.md
git log --pretty=format:"- %s (%h)" $PREV_TAG..HEAD >> release_notes.md
else
echo "### Initial Release" >> release_notes.md
echo "- First release of PDDL MCP Server" >> release_notes.md
fi
echo "" >> release_notes.md
echo "## Installation" >> release_notes.md
echo "" >> release_notes.md
echo "### From Source" >> release_notes.md
echo "\`\`\`bash" >> release_notes.md
echo "git clone https://github.com/${{ github.repository }}.git" >> release_notes.md
echo "cd pddl-mcp-server" >> release_notes.md
echo "pip install -r requirements.txt" >> release_notes.md
echo "python server.py" >> release_notes.md
echo "\`\`\`" >> release_notes.md
echo "" >> release_notes.md
echo "### Standalone Executables" >> release_notes.md
echo "Download the appropriate executable for your platform from the assets below." >> release_notes.md
echo "" >> release_notes.md
echo "## Documentation" >> release_notes.md
echo "" >> release_notes.md
echo "- [README](README.md) - Getting started guide" >> release_notes.md
echo "- [Testing Guide](TESTING_GUIDE.md) - How to test the server" >> release_notes.md
echo "- [Contributing](CONTRIBUTING.md) - How to contribute" >> release_notes.md
echo "" >> release_notes.md
echo "## Support" >> release_notes.md
echo "" >> release_notes.md
echo "- [Issues](https://github.com/${{ github.repository }}/issues) - Report bugs or request features" >> release_notes.md
echo "- [Discussions](https://github.com/${{ github.repository }}/discussions) - Community discussions" >> release_notes.md
# Set output for use in release
echo "release_notes<<EOF" >> $GITHUB_OUTPUT
cat release_notes.md >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Create or update tag
if: github.event_name == 'workflow_dispatch'
run: |
VERSION="${{ needs.validate.outputs.version }}"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag -a "$VERSION" -m "Release $VERSION"
git push origin "$VERSION"
- name: Create GitHub Release
uses: actions/create-release@v1
id: create_release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ needs.validate.outputs.version }}
release_name: Release ${{ needs.validate.outputs.version }}
body: ${{ steps.release_notes.outputs.release_notes }}
draft: false
prerelease: ${{ needs.validate.outputs.is_prerelease }}
- name: Upload release assets
run: |
UPLOAD_URL="${{ steps.create_release.outputs.upload_url }}"
# Upload archives
for file in artifacts/release-*/pddl-mcp-server-*.{tar.gz,zip}; do
if [ -f "$file" ]; then
filename=$(basename "$file")
echo "Uploading $filename"
curl -X POST \
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
-H "Content-Type: application/octet-stream" \
--data-binary @"$file" \
"${UPLOAD_URL%\{*}?name=$filename"
fi
done
# Upload Python packages (from ubuntu build)
for file in artifacts/release-Linux/dist/*.{whl,tar.gz}; do
if [ -f "$file" ]; then
filename=$(basename "$file")
echo "Uploading $filename"
curl -X POST \
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
-H "Content-Type: application/octet-stream" \
--data-binary @"$file" \
"${UPLOAD_URL%\{*}?name=$filename"
fi
done
publish_pypi:
name: Publish to PyPI
runs-on: ubuntu-latest
needs: [validate, test, create_release]
if: ${{ !needs.validate.outputs.is_prerelease }}
environment: pypi
steps:
- name: Download build artifacts
uses: actions/download-artifact@v3
with:
name: release-Linux
path: dist
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.PYPI_API_TOKEN }}
packages_dir: dist/dist/
notify:
name: Notify Release
runs-on: ubuntu-latest
needs: [validate, create_release]
if: always()
steps:
- name: Notify success
if: ${{ needs.create_release.result == 'success' }}
run: |
echo "🎉 Release ${{ needs.validate.outputs.version }} created successfully!"
echo "📦 Assets uploaded and ready for download"
echo "🔗 Release URL: https://github.com/${{ github.repository }}/releases/tag/${{ needs.validate.outputs.version }}"
- name: Notify failure
if: ${{ needs.create_release.result == 'failure' }}
run: |
echo "❌ Release creation failed"
echo "Please check the logs and try again"
exit 1