name: Manual Release on: workflow_dispatch: inputs: version: description: 'Version to release (e.g., 0.1.1)' required: true type: string test_pypi: description: 'Test on TestPyPI first' required: false type: boolean default: true jobs: validate-and-release: runs-on: ubuntu-latest permissions: contents: write actions: read steps: - uses: actions/checkout@v4 with: token: ${{ secrets.GITHUB_TOKEN }} - name: Check CI status run: | echo "ℹ️ This workflow will download build artifacts from the latest CI run." echo " CI must have completed successfully on the current commit." echo "" - name: Validate version format run: | if ! [[ "${{ inputs.version }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "❌ Invalid version format. Use semantic versioning (e.g., 0.1.1)" exit 1 fi echo "✅ Version format valid: ${{ inputs.version }}" - name: Check if version already exists run: | if git tag | grep -q "^v${{ inputs.version }}$"; then echo "❌ Version v${{ inputs.version }} already exists!" exit 1 fi echo "✅ Version is new" - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.13' - name: Install uv run: | curl -LsSf https://astral.sh/uv/install.sh | sh echo "$HOME/.cargo/bin" >> $GITHUB_PATH - name: Update versions run: | ./scripts/bump_version.sh ${{ inputs.version }} git config user.name "GitHub Actions" git config user.email "actions@github.com" git add packages/*/pyproject.toml git commit -m "chore: release v${{ inputs.version }}" - name: Get CI run ID id: get-ci-run run: | # Get the latest successful CI run on the previous commit (before version bump) COMMIT_SHA=$(git rev-parse HEAD~1) RUN_ID=$(gh run list \ --workflow="CI - Build and Test" \ --status=success \ --commit=$COMMIT_SHA \ --json databaseId \ --jq '.[0].databaseId') if [ -z "$RUN_ID" ]; then echo "❌ No successful CI run found for commit $COMMIT_SHA" echo "" echo "This usually means:" echo "1. CI hasn't run on the latest commit yet" echo "2. CI failed on the latest commit" echo "" echo "Please ensure CI passes on main branch before releasing." exit 1 fi echo "✅ Found CI run: $RUN_ID" echo "run-id=$RUN_ID" >> $GITHUB_OUTPUT env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Download artifacts from CI run: | echo "📦 Downloading artifacts from CI run ${{ steps.get-ci-run.outputs.run-id }}..." # Download all wheel artifacts gh run download ${{ steps.get-ci-run.outputs.run-id }} \ --pattern "wheels-*" \ --dir ./dist-downloads # Consolidate all wheels into packages/*/dist/ mkdir -p packages/leann-core/dist mkdir -p packages/leann-backend-hnsw/dist mkdir -p packages/leann-backend-diskann/dist mkdir -p packages/leann/dist find ./dist-downloads -name "*.whl" -exec cp {} ./packages/ \; # Move wheels to correct package directories for wheel in packages/*.whl; do if [[ $wheel == *"leann_core"* ]]; then mv "$wheel" packages/leann-core/dist/ elif [[ $wheel == *"leann_backend_hnsw"* ]]; then mv "$wheel" packages/leann-backend-hnsw/dist/ elif [[ $wheel == *"leann_backend_diskann"* ]]; then mv "$wheel" packages/leann-backend-diskann/dist/ elif [[ $wheel == *"leann-"* ]] && [[ $wheel != *"backend"* ]] && [[ $wheel != *"core"* ]]; then mv "$wheel" packages/leann/dist/ fi done # List downloaded wheels echo "✅ Downloaded wheels:" find packages/*/dist -name "*.whl" -type f | sort env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Test on TestPyPI (optional) if: inputs.test_pypi continue-on-error: true env: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.TEST_PYPI_API_TOKEN }} run: | if [ -z "$TWINE_PASSWORD" ]; then echo "⚠️ TEST_PYPI_API_TOKEN not configured, skipping TestPyPI upload" echo " To enable TestPyPI testing, add TEST_PYPI_API_TOKEN to repository secrets" exit 0 fi pip install twine echo "📦 Uploading to TestPyPI..." twine upload --repository testpypi packages/*/dist/* --verbose || { echo "⚠️ TestPyPI upload failed, but continuing with release" echo " This is optional and won't block the release" exit 0 } echo "✅ Test upload successful!" echo "📋 Check packages at: https://test.pypi.org/user/your-username/" echo "" echo "To test installation:" echo "pip install -i https://test.pypi.org/simple/ leann" - name: Publish to PyPI env: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} run: | if [ -z "$TWINE_PASSWORD" ]; then echo "❌ PYPI_API_TOKEN not configured!" echo " Please add PYPI_API_TOKEN to repository secrets" exit 1 fi pip install twine echo "📦 Publishing to PyPI..." # Collect all wheels in one place mkdir -p all_wheels find packages/*/dist -name "*.whl" -exec cp {} all_wheels/ \; find packages/*/dist -name "*.tar.gz" -exec cp {} all_wheels/ \; echo "📋 Packages to publish:" ls -la all_wheels/ # Upload to PyPI twine upload all_wheels/* --skip-existing --verbose echo "✅ Published to PyPI!" echo "🎉 Check packages at: https://pypi.org/project/leann/" - name: Create and push tag run: | git tag "v${{ inputs.version }}" git push origin main git push origin "v${{ inputs.version }}" echo "✅ Tag v${{ inputs.version }} created and pushed" - name: Create GitHub Release uses: softprops/action-gh-release@v1 with: tag_name: v${{ inputs.version }} name: Release v${{ inputs.version }} body: | ## 🚀 Release v${{ inputs.version }} ### What's Changed See the [full changelog](https://github.com/${{ github.repository }}/compare/...v${{ inputs.version }}) ### Installation ```bash pip install leann==${{ inputs.version }} ``` ### Test Installation (if using TestPyPI) ```bash pip install -i https://test.pypi.org/simple/ leann==${{ inputs.version }} ``` draft: false prerelease: false env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Summary run: | echo "✅ Release v${{ inputs.version }} completed!" echo "" echo "📊 What was done:" echo "1. ✅ Downloaded build artifacts from CI" echo "2. ✅ Updated version numbers to ${{ inputs.version }}" echo "3. ✅ Published packages to PyPI" echo "4. ✅ Created git tag v${{ inputs.version }}" echo "5. ✅ Created GitHub Release" echo "" echo "🎉 Installation:" echo "pip install leann==${{ inputs.version }}"