name: Publish to PyPI on: # Trigger on version tags push: tags: - 'v*' # Allow manual triggering for testing workflow_dispatch: inputs: test_pypi: description: 'Publish to TestPyPI instead of PyPI' required: false default: false type: boolean skip_existing: description: 'Skip if version already exists' required: false default: true type: boolean env: PYTHON_VERSION: "3.11" jobs: # Ensure all tests pass before publishing validate: runs-on: ubuntu-latest steps: - 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 -e .[dev,test] - name: Run validation tests run: | python run_tests.py --validate python run_tests.py --type unit python run_tests.py --lint # Build the package build: runs-on: ubuntu-latest needs: validate outputs: version: ${{ steps.version.outputs.version }} is_prerelease: ${{ steps.version.outputs.is_prerelease }} steps: - uses: actions/checkout@v4 with: fetch-depth: 0 # Fetch full history for proper version detection - name: Set up Python uses: actions/setup-python@v4 with: python-version: ${{ env.PYTHON_VERSION }} - name: Install build dependencies run: | python -m pip install --upgrade pip pip install build twine - name: Extract version info id: version run: | VERSION=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])") echo "version=$VERSION" >> $GITHUB_OUTPUT # Check if this is a prerelease (contains alpha, beta, rc, or dev) if echo "$VERSION" | grep -qE "(a|b|rc|dev)"; then echo "is_prerelease=true" >> $GITHUB_OUTPUT else echo "is_prerelease=false" >> $GITHUB_OUTPUT fi echo "Detected version: $VERSION" - name: Build package run: python -m build - name: Verify package run: | python -m twine check dist/* ls -la dist/ # Check that version matches tag (if triggered by tag) if [[ "$GITHUB_REF" == refs/tags/* ]]; then TAG_VERSION=${GITHUB_REF#refs/tags/v} PACKAGE_VERSION="${{ steps.version.outputs.version }}" if [[ "$TAG_VERSION" != "$PACKAGE_VERSION" ]]; then echo "Error: Tag version ($TAG_VERSION) doesn't match package version ($PACKAGE_VERSION)" exit 1 fi fi - name: Upload build artifacts uses: actions/upload-artifact@v3 with: name: dist-${{ steps.version.outputs.version }} path: dist/ retention-days: 30 # Publish to TestPyPI (for testing) publish-test: runs-on: ubuntu-latest needs: build if: | github.event_name == 'workflow_dispatch' && github.event.inputs.test_pypi == 'true' environment: name: testpypi url: https://test.pypi.org/p/vultr-dns-mcp permissions: id-token: write # Required for trusted publishing steps: - name: Download build artifacts uses: actions/download-artifact@v3 with: name: dist-${{ needs.build.outputs.version }} path: dist/ - name: Publish to TestPyPI uses: pypa/gh-action-pypi-publish@release/v1 with: repository-url: https://test.pypi.org/legacy/ skip-existing: ${{ github.event.inputs.skip_existing == 'true' }} print-hash: true # Publish to PyPI (production) publish: runs-on: ubuntu-latest needs: build if: | (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')) || (github.event_name == 'workflow_dispatch' && github.event.inputs.test_pypi != 'true') environment: name: pypi url: https://pypi.org/p/vultr-dns-mcp permissions: id-token: write # Required for trusted publishing contents: write # Required for GitHub releases steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Download build artifacts uses: actions/download-artifact@v3 with: name: dist-${{ needs.build.outputs.version }} path: dist/ - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: skip-existing: ${{ github.event.inputs.skip_existing == 'true' }} print-hash: true # Create GitHub release - name: Generate changelog id: changelog run: | if [[ -f CHANGELOG.md ]]; then # Extract changelog for current version VERSION="${{ needs.build.outputs.version }}" awk "/^## \[?$VERSION\]?/ {found=1; next} /^## / {found=0} found {print}" CHANGELOG.md > current_changelog.md if [[ -s current_changelog.md ]]; then echo "changelog_file=current_changelog.md" >> $GITHUB_OUTPUT else echo "No changelog entry found for version $VERSION" echo "changelog_file=" >> $GITHUB_OUTPUT fi else echo "No CHANGELOG.md found" echo "changelog_file=" >> $GITHUB_OUTPUT fi - name: Create GitHub Release uses: softprops/action-gh-release@v1 if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') with: name: Release v${{ needs.build.outputs.version }} tag_name: ${{ github.ref_name }} body_path: ${{ steps.changelog.outputs.changelog_file }} files: dist/* prerelease: ${{ needs.build.outputs.is_prerelease == 'true' }} generate_release_notes: ${{ steps.changelog.outputs.changelog_file == '' }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Test installation from PyPI test-published: runs-on: ubuntu-latest needs: [build, publish] if: success() strategy: matrix: python-version: ["3.8", "3.11", "3.12"] steps: - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Wait for PyPI propagation run: sleep 60 # Give PyPI some time to propagate - name: Install from PyPI run: | pip install --upgrade pip pip install vultr-dns-mcp==${{ needs.build.outputs.version }} - name: Test installation run: | vultr-dns-mcp --version vultr-dns-mcp --help python -c "import vultr_dns_mcp; print(f'✅ vultr-dns-mcp v{vultr_dns_mcp.__version__} installed successfully')" # Notify on completion notify: runs-on: ubuntu-latest needs: [build, publish, test-published] if: always() steps: - name: Publish Summary run: | echo "## 📦 PyPI Publication Summary" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**Version:** ${{ needs.build.outputs.version }}" >> $GITHUB_STEP_SUMMARY echo "**Pre-release:** ${{ needs.build.outputs.is_prerelease }}" >> $GITHUB_STEP_SUMMARY echo "**Repository:** https://pypi.org/project/vultr-dns-mcp/" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY if [[ "${{ needs.publish.result }}" == "success" ]]; then echo "✅ **Status:** Successfully published to PyPI" >> $GITHUB_STEP_SUMMARY else echo "❌ **Status:** Publication failed" >> $GITHUB_STEP_SUMMARY fi if [[ "${{ needs.test-published.result }}" == "success" ]]; then echo "✅ **Installation Test:** Passed" >> $GITHUB_STEP_SUMMARY else echo "❌ **Installation Test:** Failed or skipped" >> $GITHUB_STEP_SUMMARY fi