unity-mcp/.github/workflows/release.yml

406 lines
13 KiB
YAML

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: Merge beta into main
shell: bash
run: |
set -euo pipefail
git config user.name "GitHub Actions"
git config user.email "actions@github.com"
# Fetch beta branch
git fetch origin beta
# Check if beta has changes not in main
if git merge-base --is-ancestor origin/beta HEAD; then
echo "beta is already merged into main. Nothing to merge."
else
echo "Merging beta into main..."
git merge origin/beta --no-edit -m "chore: merge beta into main for release"
echo "Beta merged successfully."
fi
- name: Strip beta suffix from version if present
shell: bash
run: |
set -euo pipefail
CURRENT_VERSION=$(jq -r '.version' "MCPForUnity/package.json")
echo "Current version: $CURRENT_VERSION"
# Strip beta/alpha/rc suffix if present (e.g., "9.4.0-beta.1" -> "9.4.0")
if [[ "$CURRENT_VERSION" == *"-"* ]]; then
STABLE_VERSION=$(echo "$CURRENT_VERSION" | sed -E 's/-[a-zA-Z]+\.[0-9]+$//')
# Validate we have a proper X.Y.Z format after stripping
if ! [[ "$STABLE_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Error: Could not parse version '$CURRENT_VERSION' -> '$STABLE_VERSION'" >&2
exit 1
fi
echo "Stripping prerelease suffix: $CURRENT_VERSION -> $STABLE_VERSION"
jq --arg v "$STABLE_VERSION" '.version = $v' MCPForUnity/package.json > tmp.json
mv tmp.json MCPForUnity/package.json
# Also update pyproject.toml
sed -i "s/^version = .*/version = \"${STABLE_VERSION}\"/" Server/pyproject.toml
else
echo "Version is already stable: $CURRENT_VERSION"
fi
- 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
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