name: Release concurrency: group: release-main cancel-in-progress: false on: workflow_dispatch: inputs: version_bump: description: "Version bump type" type: choice options: - patch - minor - major default: patch required: true jobs: bump: name: Bump version, tag, and create release runs-on: ubuntu-latest permissions: contents: write pull-requests: write outputs: new_version: ${{ steps.compute.outputs.new_version }} tag: ${{ steps.tag.outputs.tag }} bump_branch: ${{ steps.bump_branch.outputs.name }} steps: - name: Ensure workflow is running on main shell: bash run: | set -euo pipefail if [[ "${GITHUB_REF_NAME}" != "main" ]]; then echo "This workflow must be run on the main branch. Current ref: ${GITHUB_REF_NAME}" >&2 exit 1 fi - name: Checkout repository uses: actions/checkout@v6 with: ref: main fetch-depth: 0 - name: Compute new version id: compute shell: bash run: | set -euo pipefail BUMP="${{ inputs.version_bump }}" CURRENT_VERSION=$(jq -r '.version' "MCPForUnity/package.json") echo "Current version: $CURRENT_VERSION" IFS='.' read -r MA MI PA <<< "$CURRENT_VERSION" case "$BUMP" in major) ((MA+=1)); MI=0; PA=0 ;; minor) ((MI+=1)); PA=0 ;; patch) ((PA+=1)) ;; *) echo "Unknown version_bump: $BUMP" >&2 exit 1 ;; esac NEW_VERSION="$MA.$MI.$PA" echo "New version: $NEW_VERSION" echo "new_version=$NEW_VERSION" >> "$GITHUB_OUTPUT" echo "current_version=$CURRENT_VERSION" >> "$GITHUB_OUTPUT" - name: Compute tag id: tag env: NEW_VERSION: ${{ steps.compute.outputs.new_version }} shell: bash run: | set -euo pipefail echo "tag=v${NEW_VERSION}" >> "$GITHUB_OUTPUT" - name: Update files to new version env: NEW_VERSION: ${{ steps.compute.outputs.new_version }} shell: bash run: | set -euo pipefail echo "Updating all version references to $NEW_VERSION" python3 tools/update_versions.py --version "$NEW_VERSION" - name: Commit version bump to a temporary branch id: bump_branch env: NEW_VERSION: ${{ steps.compute.outputs.new_version }} shell: bash run: | set -euo pipefail BRANCH="release/v${NEW_VERSION}" echo "name=$BRANCH" >> "$GITHUB_OUTPUT" git config user.name "GitHub Actions" git config user.email "actions@github.com" git checkout -b "$BRANCH" git add MCPForUnity/package.json manifest.json "Server/pyproject.toml" Server/README.md README.md docs/i18n/README-zh.md if git diff --cached --quiet; then echo "No version changes to commit." else git commit -m "chore: bump version to ${NEW_VERSION}" fi echo "Pushing bump branch $BRANCH" git push origin "$BRANCH" - name: Create PR for version bump into main id: bump_pr env: GH_TOKEN: ${{ github.token }} NEW_VERSION: ${{ steps.compute.outputs.new_version }} BRANCH: ${{ steps.bump_branch.outputs.name }} shell: bash run: | set -euo pipefail PR_URL=$(gh pr create \ --base main \ --head "$BRANCH" \ --title "chore: bump version to ${NEW_VERSION}" \ --body "Automated version bump to ${NEW_VERSION}.") echo "pr_url=$PR_URL" >> "$GITHUB_OUTPUT" PR_NUMBER=$(echo "$PR_URL" | grep -oE '[0-9]+$') echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT" - name: Enable auto-merge and merge PR env: GH_TOKEN: ${{ github.token }} PR_NUMBER: ${{ steps.bump_pr.outputs.pr_number }} shell: bash run: | set -euo pipefail # Enable auto-merge (requires repo setting "Allow auto-merge") gh pr merge "$PR_NUMBER" --merge --auto || true # Wait for PR to be merged (poll up to 2 minutes) for i in {1..24}; do STATE=$(gh pr view "$PR_NUMBER" --json state -q '.state') if [[ "$STATE" == "MERGED" ]]; then echo "PR merged successfully." exit 0 fi echo "Waiting for PR to merge... (state: $STATE)" sleep 5 done echo "PR did not merge in time. Attempting direct merge..." gh pr merge "$PR_NUMBER" --merge - name: Fetch merged main and create tag env: TAG: ${{ steps.tag.outputs.tag }} shell: bash run: | set -euo pipefail git fetch origin main git checkout main git pull origin main echo "Preparing to create tag $TAG" if git ls-remote --tags origin | grep -q "refs/tags/$TAG$"; then echo "Tag $TAG already exists on remote. Refusing to release." >&2 exit 1 fi git tag -a "$TAG" -m "Version ${TAG#v}" git push origin "$TAG" - name: Clean up release branch if: always() env: GH_TOKEN: ${{ github.token }} BRANCH: ${{ steps.bump_branch.outputs.name }} shell: bash run: | set -euo pipefail git push origin --delete "$BRANCH" || true - name: Create GitHub release uses: softprops/action-gh-release@v2 with: tag_name: ${{ steps.tag.outputs.tag }} name: ${{ steps.tag.outputs.tag }} generate_release_notes: true sync_beta: name: Merge main back into beta via PR needs: - bump runs-on: ubuntu-latest permissions: contents: write pull-requests: write steps: - name: Checkout main uses: actions/checkout@v6 with: ref: main fetch-depth: 0 - name: Create PR to merge main into beta id: sync_pr env: GH_TOKEN: ${{ github.token }} NEW_VERSION: ${{ needs.bump.outputs.new_version }} shell: bash run: | set -euo pipefail # Check if beta is behind main git fetch origin beta if git merge-base --is-ancestor origin/main origin/beta; then echo "beta is already up to date with main. Skipping PR." echo "skipped=true" >> "$GITHUB_OUTPUT" exit 0 fi PR_URL=$(gh pr create \ --base beta \ --head main \ --title "chore: sync main (v${NEW_VERSION}) into beta" \ --body "Automated sync of version bump from main into beta.") echo "pr_url=$PR_URL" >> "$GITHUB_OUTPUT" PR_NUMBER=$(echo "$PR_URL" | grep -oE '[0-9]+$') echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT" echo "skipped=false" >> "$GITHUB_OUTPUT" - name: Enable auto-merge and merge sync PR if: steps.sync_pr.outputs.skipped != 'true' env: GH_TOKEN: ${{ github.token }} PR_NUMBER: ${{ steps.sync_pr.outputs.pr_number }} shell: bash run: | set -euo pipefail # Enable auto-merge (requires repo setting "Allow auto-merge") gh pr merge "$PR_NUMBER" --merge --auto || true # Wait for PR to be merged (poll up to 2 minutes) for i in {1..24}; do STATE=$(gh pr view "$PR_NUMBER" --json state -q '.state') if [[ "$STATE" == "MERGED" ]]; then echo "Sync PR merged successfully." exit 0 fi echo "Waiting for sync PR to merge... (state: $STATE)" sleep 5 done echo "Sync PR did not merge in time. Attempting direct merge..." gh pr merge "$PR_NUMBER" --merge publish_docker: name: Publish Docker image needs: - bump runs-on: ubuntu-latest permissions: contents: read steps: - name: Check out the repo uses: actions/checkout@v6 with: ref: ${{ needs.bump.outputs.tag }} fetch-depth: 0 - name: Build and push Docker image uses: ./.github/actions/publish-docker with: docker_username: ${{ secrets.DOCKER_USERNAME }} docker_password: ${{ secrets.DOCKER_PASSWORD }} image: ${{ secrets.DOCKER_USERNAME }}/mcp-for-unity-server version: ${{ needs.bump.outputs.new_version }} include_branch_tags: "false" context: . dockerfile: Server/Dockerfile platforms: linux/amd64 publish_pypi: name: Publish Python distribution to PyPI needs: - bump runs-on: ubuntu-latest environment: name: pypi url: https://pypi.org/p/mcpforunityserver permissions: contents: read id-token: write steps: - uses: actions/checkout@v6 with: ref: ${{ needs.bump.outputs.tag }} fetch-depth: 0 # Inlined from .github/actions/publish-pypi to avoid nested composite action issue # with pypa/gh-action-pypi-publish (see https://github.com/pypa/gh-action-pypi-publish/issues/338) - name: Install uv uses: astral-sh/setup-uv@v7 with: version: "latest" enable-cache: true cache-dependency-glob: "Server/uv.lock" - name: Build a binary wheel and a source tarball shell: bash run: uv build working-directory: ./Server - name: Publish distribution to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: packages-dir: Server/dist/ publish_mcpb: name: Generate and publish MCPB bundle needs: - bump runs-on: ubuntu-latest permissions: contents: write steps: - name: Check out the repo uses: actions/checkout@v6 with: ref: ${{ needs.bump.outputs.tag }} fetch-depth: 0 - name: Set up Node.js uses: actions/setup-node@v4 with: node-version: "20" - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.11" - name: Generate MCPB bundle env: NEW_VERSION: ${{ needs.bump.outputs.new_version }} shell: bash run: | set -euo pipefail python3 tools/generate_mcpb.py "$NEW_VERSION" \ --output "unity-mcp-${NEW_VERSION}.mcpb" \ --icon docs/images/coplay-logo.png - name: Upload MCPB to release uses: softprops/action-gh-release@v2 with: tag_name: ${{ needs.bump.outputs.tag }} files: unity-mcp-${{ needs.bump.outputs.new_version }}.mcpb