Skip to content

Release Automation

Release Automation #18

Workflow file for this run

name: Release Automation
on:
workflow_dispatch:
inputs:
release_core:
description: 'Release JEngine.Core?'
required: true
type: boolean
default: false
core_version:
description: 'New Core version (e.g., 1.0.6)'
required: false
type: string
release_util:
description: 'Release JEngine.Util?'
required: true
type: boolean
default: false
util_version:
description: 'New Util version (e.g., 1.0.1)'
required: false
type: string
release_ui:
description: 'Release JEngine.UI?'
required: true
type: boolean
default: false
ui_version:
description: 'New UI version (e.g., 1.0.0)'
required: false
type: string
manual_changelog:
description: 'Manual changelog entries (optional)'
required: false
type: string
permissions: read-all
jobs:
validate:
name: Validate Inputs
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
core_version: ${{ steps.validate.outputs.core_version }}
util_version: ${{ steps.validate.outputs.util_version }}
ui_version: ${{ steps.validate.outputs.ui_version }}
release_tag: ${{ steps.validate.outputs.release_tag }}
create_github_release: ${{ steps.validate.outputs.create_github_release }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Validate inputs
id: validate
run: |
# Check at least one package is selected
if [ "${{ inputs.release_core }}" != "true" ] && [ "${{ inputs.release_util }}" != "true" ] && [ "${{ inputs.release_ui }}" != "true" ]; then
echo "Error: At least one package must be selected for release"
exit 1
fi
# Validate semantic version format
validate_version() {
local version=$1
if ! [[ $version =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Error: Invalid version format '$version'. Must be X.Y.Z (e.g., 1.0.6)"
exit 1
fi
}
# Get current versions from package.json
CURRENT_CORE_VERSION=$(jq -r '.version' UnityProject/Packages/com.jasonxudeveloper.jengine.core/package.json)
CURRENT_UTIL_VERSION=$(jq -r '.version' UnityProject/Packages/com.jasonxudeveloper.jengine.util/package.json)
CURRENT_UI_VERSION=$(jq -r '.version' UnityProject/Packages/com.jasonxudeveloper.jengine.ui/package.json)
echo "Current Core version: $CURRENT_CORE_VERSION"
echo "Current Util version: $CURRENT_UTIL_VERSION"
echo "Current UI version: $CURRENT_UI_VERSION"
# Validate Core version if releasing
if [ "${{ inputs.release_core }}" == "true" ]; then
if [ -z "${{ inputs.core_version }}" ]; then
echo "Error: Core version is required when releasing Core package"
exit 1
fi
validate_version "${{ inputs.core_version }}"
# Compare versions (simple string comparison for semantic versions)
if [ "${{ inputs.core_version }}" == "$CURRENT_CORE_VERSION" ]; then
echo "Error: New Core version must be different from current version"
exit 1
fi
echo "core_version=${{ inputs.core_version }}" >> $GITHUB_OUTPUT
else
echo "core_version=$CURRENT_CORE_VERSION" >> $GITHUB_OUTPUT
fi
# Validate Util version if releasing
if [ "${{ inputs.release_util }}" == "true" ]; then
if [ -z "${{ inputs.util_version }}" ]; then
echo "Error: Util version is required when releasing Util package"
exit 1
fi
validate_version "${{ inputs.util_version }}"
if [ "${{ inputs.util_version }}" == "$CURRENT_UTIL_VERSION" ]; then
echo "Error: New Util version must be different from current version"
exit 1
fi
echo "util_version=${{ inputs.util_version }}" >> $GITHUB_OUTPUT
else
echo "util_version=$CURRENT_UTIL_VERSION" >> $GITHUB_OUTPUT
fi
# Validate UI version if releasing
if [ "${{ inputs.release_ui }}" == "true" ]; then
if [ -z "${{ inputs.ui_version }}" ]; then
echo "Error: UI version is required when releasing UI package"
exit 1
fi
validate_version "${{ inputs.ui_version }}"
if [ "${{ inputs.ui_version }}" == "$CURRENT_UI_VERSION" ]; then
echo "Error: New UI version must be different from current version"
exit 1
fi
echo "ui_version=${{ inputs.ui_version }}" >> $GITHUB_OUTPUT
else
echo "ui_version=$CURRENT_UI_VERSION" >> $GITHUB_OUTPUT
fi
# Release tag always follows Core version
# GitHub releases are only created when Core is released
if [ "${{ inputs.release_core }}" == "true" ]; then
# No 'v' prefix to match existing tag format (1.0.5, not v1.0.5)
echo "release_tag=${{ inputs.core_version }}" >> $GITHUB_OUTPUT
echo "create_github_release=true" >> $GITHUB_OUTPUT
else
# If only Util is released, create tag for OpenUPM but no GitHub release
# No 'v' prefix to match existing tag format
echo "release_tag=util-${{ inputs.util_version }}" >> $GITHUB_OUTPUT
echo "create_github_release=false" >> $GITHUB_OUTPUT
fi
echo "✅ Validation passed"
run-tests:
name: Run Unity Tests
needs: validate
permissions:
contents: read
checks: write
uses: ./.github/workflows/unity-tests.yml
secrets: inherit
upload-coverage:
name: Upload Coverage
needs: run-tests
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Download coverage artifacts
uses: actions/download-artifact@v4
with:
name: Coverage-results-2022.3.55f1
path: coverage
- name: List coverage files
run: |
echo "Coverage directory structure:"
find coverage -type f -name "*.xml" 2>/dev/null || echo "No XML files found"
- name: Fix coverage paths
run: |
# Unity test runner generates paths with /github/workspace/ prefix (Docker container path)
# Strip this prefix so Codecov can match paths to repository files
echo "Fixing coverage paths..."
find coverage -name "*.xml" -exec sed -i 's|/github/workspace/||g' {} \;
echo "Path fix complete. Sample paths after fix:"
find coverage -name "TestCoverageResults*.xml" -exec grep -h "fullPath=" {} \; | head -5 || true
- name: Upload coverage to Codecov (util package)
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: coverage/**/TestCoverageResults*.xml
flags: util
name: jengine-util
fail_ci_if_error: true
verbose: true
- name: Upload coverage to Codecov (ui package)
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: coverage/**/TestCoverageResults*.xml
flags: ui
name: jengine-ui
fail_ci_if_error: true
verbose: true
prepare-release:
name: Prepare Release
needs: [validate, run-tests]
runs-on: ubuntu-latest
permissions:
contents: write
outputs:
changelog: ${{ steps.generate-changelog.outputs.changelog }}
steps:
# Generate GitHub App token for authenticated commits
- name: Generate GitHub App Token
id: generate-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.RELEASE_APP_ID }}
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
- name: Checkout repository
uses: actions/checkout@v4
with:
token: ${{ steps.generate-token.outputs.token }}
fetch-depth: 0 # Full history for changelog generation
# Determine the tag to use for changelog generation
- name: Determine changelog base tag
id: base-tag
run: |
# Find the latest existing semantic version tag (e.g., 1.0.5, not v1.0.5)
# Sort by version number and get the latest one
BASE_TAG=$(git tag -l '[0-9]*.[0-9]*.[0-9]*' | sort -V | tail -n 1)
if [ -z "$BASE_TAG" ]; then
echo "Warning: No existing version tags found"
BASE_TAG=""
else
echo "Found latest tag: $BASE_TAG"
fi
echo "base_tag=$BASE_TAG" >> $GITHUB_OUTPUT
echo "Using base tag for changelog: ${BASE_TAG:-'(none - will use all commits)'}"
# Generate changelog from conventional commits
- name: Generate changelog
id: generate-changelog
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
run: |
BASE_TAG="${{ steps.base-tag.outputs.base_tag }}"
# Get commits since last tag
if [ -n "$BASE_TAG" ] && git rev-parse "$BASE_TAG" >/dev/null 2>&1; then
echo "Getting commits since tag: $BASE_TAG"
COMMITS=$(git log ${BASE_TAG}..HEAD --pretty=format:"%H|%s" --no-merges)
else
echo "Warning: No valid base tag, using all commits"
COMMITS=$(git log --pretty=format:"%H|%s" --no-merges)
fi
# Parse conventional commits
FEATURES=""
FIXES=""
BREAKING=""
OTHER=""
declare -A CONTRIBUTORS_MAP
# Store regex in variable to avoid bash parsing issues with special characters
COMMIT_PATTERN='^([a-z]+)(\(([^)]+)\))?!?:[[:space:]](.+)$'
# Pattern to extract PR number from commit message like "message (#123)"
PR_PATTERN='\(#([0-9]+)\)$'
while IFS='|' read -r hash subject; do
[ -z "$hash" ] && continue
# Extract PR number if present and convert to hyperlink
pr_link=""
if [[ $subject =~ $PR_PATTERN ]]; then
pr_num="${BASH_REMATCH[1]}"
pr_link="[#${pr_num}](https://github.com/${REPO}/pull/${pr_num})"
# Replace (#123) with the hyperlink (use | as delimiter to avoid conflict with URL slashes)
subject=$(echo "$subject" | sed "s|(#${pr_num})|(${pr_link})|")
# Get contributor from PR using GitHub API
pr_author=$(gh api "repos/${REPO}/pulls/${pr_num}" --jq '.user.login' 2>/dev/null || echo "")
if [ -n "$pr_author" ] && [ "$pr_author" != "null" ]; then
CONTRIBUTORS_MAP["$pr_author"]=1
fi
fi
# Extract commit type and scope
if [[ $subject =~ $COMMIT_PATTERN ]]; then
type="${BASH_REMATCH[1]}"
scope="${BASH_REMATCH[3]}"
description="${BASH_REMATCH[4]}"
is_breaking="${subject//[^!]/}"
# Format with scope if present
if [ -n "$scope" ]; then
entry="**$scope**: $description"
else
entry="$description"
fi
case $type in
feat)
FEATURES="${FEATURES}- $entry\n"
;;
fix)
FIXES="${FIXES}- $entry\n"
;;
# Other conventional types (chore, docs, refactor, etc.) are intentionally excluded
esac
# Check for breaking changes
if [ -n "$is_breaking" ] || git show -s --format=%B $hash | grep -q "BREAKING CHANGE:"; then
breaking_desc=$(git show -s --format=%B $hash | sed -n '/BREAKING CHANGE:/,/^$/p' | tail -n +2 | head -n 1)
if [ -z "$breaking_desc" ]; then
breaking_desc="$description"
fi
BREAKING="${BREAKING}- $breaking_desc\n"
fi
else
# Non-conventional commit - add to Other Changes
# Clean up the subject (remove quotes if present)
clean_subject=$(echo "$subject" | sed 's/^"//;s/"$//')
if [ -n "$clean_subject" ]; then
OTHER="${OTHER}- $clean_subject\n"
fi
fi
done <<< "$COMMITS"
# Build contributors list from the associative array (excludes bots)
CONTRIBUTORS=""
for contributor in "${!CONTRIBUTORS_MAP[@]}"; do
# Skip bot accounts
if [[ "$contributor" != *"[bot]"* ]] && [[ "$contributor" != *"-bot"* ]]; then
CONTRIBUTORS="${CONTRIBUTORS}[@${contributor}](https://github.com/${contributor}), "
fi
done
# Remove trailing comma
CONTRIBUTORS=$(echo "$CONTRIBUTORS" | sed 's/, $//')
# Build changelog
CHANGELOG=""
# Add package release info
RELEASED_PACKAGES=""
UNCHANGED_PACKAGES=""
if [ "${{ inputs.release_core }}" == "true" ]; then
RELEASED_PACKAGES="${RELEASED_PACKAGES}JEngine.Core v${{ needs.validate.outputs.core_version }}, "
else
UNCHANGED_PACKAGES="${UNCHANGED_PACKAGES}Core v${{ needs.validate.outputs.core_version }}, "
fi
if [ "${{ inputs.release_util }}" == "true" ]; then
RELEASED_PACKAGES="${RELEASED_PACKAGES}JEngine.Util v${{ needs.validate.outputs.util_version }}, "
else
UNCHANGED_PACKAGES="${UNCHANGED_PACKAGES}Util v${{ needs.validate.outputs.util_version }}, "
fi
if [ "${{ inputs.release_ui }}" == "true" ]; then
RELEASED_PACKAGES="${RELEASED_PACKAGES}JEngine.UI v${{ needs.validate.outputs.ui_version }}, "
else
UNCHANGED_PACKAGES="${UNCHANGED_PACKAGES}UI v${{ needs.validate.outputs.ui_version }}, "
fi
# Remove trailing comma and space
RELEASED_PACKAGES=$(echo "$RELEASED_PACKAGES" | sed 's/, $//')
UNCHANGED_PACKAGES=$(echo "$UNCHANGED_PACKAGES" | sed 's/, $//')
if [ -n "$UNCHANGED_PACKAGES" ]; then
CHANGELOG="${CHANGELOG}**Released**: ${RELEASED_PACKAGES} (${UNCHANGED_PACKAGES} unchanged)\n\n"
else
CHANGELOG="${CHANGELOG}**Released**: ${RELEASED_PACKAGES}\n\n"
fi
if [ -n "$BREAKING" ]; then
CHANGELOG="${CHANGELOG}### ⚠️ BREAKING CHANGES\n\n${BREAKING}\n"
fi
if [ -n "$FEATURES" ]; then
CHANGELOG="${CHANGELOG}### ✨ Features\n\n${FEATURES}\n"
fi
if [ -n "$FIXES" ]; then
CHANGELOG="${CHANGELOG}### 🐛 Bug Fixes\n\n${FIXES}\n"
fi
# Add other changes (non-feat/fix conventional commits and non-conventional commits)
if [ -n "$OTHER" ]; then
CHANGELOG="${CHANGELOG}### 📦 Other Changes\n\n${OTHER}\n"
fi
# Add manual changelog if provided
if [ -n "${{ inputs.manual_changelog }}" ]; then
CHANGELOG="${CHANGELOG}### 📝 Additional Changes\n\n${{ inputs.manual_changelog }}\n\n"
fi
# Add contributors
if [ -n "$CONTRIBUTORS" ]; then
CHANGELOG="${CHANGELOG}### 👥 Contributors\n\n${CONTRIBUTORS}\n"
fi
# If no changelog content, add placeholder
if [ -z "$FEATURES" ] && [ -z "$FIXES" ] && [ -z "$BREAKING" ] && [ -z "$OTHER" ] && [ -z "${{ inputs.manual_changelog }}" ]; then
CHANGELOG="${CHANGELOG}Minor updates and improvements.\n"
fi
echo "changelog<<EOF" >> $GITHUB_OUTPUT
echo -e "$CHANGELOG" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
# Save to file for CHANGE.md update
echo -e "$CHANGELOG" > /tmp/changelog.txt
# Update package.json files
- name: Update Core package.json
if: inputs.release_core == true
run: |
jq '.version = "${{ needs.validate.outputs.core_version }}"' \
UnityProject/Packages/com.jasonxudeveloper.jengine.core/package.json > /tmp/package.json
mv /tmp/package.json UnityProject/Packages/com.jasonxudeveloper.jengine.core/package.json
echo "✅ Updated Core package.json to v${{ needs.validate.outputs.core_version }}"
- name: Update Util package.json
if: inputs.release_util == true
run: |
jq '.version = "${{ needs.validate.outputs.util_version }}"' \
UnityProject/Packages/com.jasonxudeveloper.jengine.util/package.json > /tmp/package.json
mv /tmp/package.json UnityProject/Packages/com.jasonxudeveloper.jengine.util/package.json
echo "✅ Updated Util package.json to v${{ needs.validate.outputs.util_version }}"
- name: Update UI package.json
if: inputs.release_ui == true
run: |
jq '.version = "${{ needs.validate.outputs.ui_version }}"' \
UnityProject/Packages/com.jasonxudeveloper.jengine.ui/package.json > /tmp/package.json
mv /tmp/package.json UnityProject/Packages/com.jasonxudeveloper.jengine.ui/package.json
echo "✅ Updated UI package.json to v${{ needs.validate.outputs.ui_version }}"
# Update README files (only when releasing Core)
- name: Update README.md
if: inputs.release_core == true
run: |
VERSION="${{ needs.validate.outputs.core_version }}"
CHANGELOG=$(cat /tmp/changelog.txt)
# Extract feature bullet points from changelog for README
FEATURES=""
if echo "$CHANGELOG" | grep -q "### ✨ Features"; then
FEATURES=$(echo "$CHANGELOG" | sed -n '/### ✨ Features/,/^###/p' | grep "^- " || true)
fi
if echo "$CHANGELOG" | grep -q "### 🐛 Bug Fixes"; then
FIXES=$(echo "$CHANGELOG" | sed -n '/### 🐛 Bug Fixes/,/^###/p' | grep "^- " || true)
if [ -n "$FEATURES" ] && [ -n "$FIXES" ]; then
FEATURES="${FEATURES}"$'\n'"${FIXES}"
elif [ -n "$FIXES" ]; then
FEATURES="$FIXES"
fi
fi
# Trim leading/trailing whitespace and empty lines
FEATURES=$(echo "$FEATURES" | sed '/^$/d')
# If no features/fixes, use a generic message
if [ -z "$FEATURES" ]; then
FEATURES="- Minor updates and improvements"
fi
# Write replacement content to temp file (avoids sed multiline issues)
{
echo "## 🎉 Latest Features (v$VERSION)"
echo ""
echo "$FEATURES"
echo ""
echo "[📋 View Complete Changelog](CHANGE.md)"
} > /tmp/new_section.txt
# Use awk for reliable multiline replacement
awk '
/^## 🎉 Latest Features/ { skip=1; while((getline line < "/tmp/new_section.txt") > 0) print line; close("/tmp/new_section.txt") }
/^\[📋 View Complete Changelog\]/ { skip=0; next }
!skip { print }
' README.md > /tmp/README.md.new
mv /tmp/README.md.new README.md
echo "✅ Updated README.md with new features"
- name: Update README_zh_cn.md
if: inputs.release_core == true
run: |
VERSION="${{ needs.validate.outputs.core_version }}"
CHANGELOG=$(cat /tmp/changelog.txt)
# Extract feature bullet points from changelog for README
FEATURES=""
if echo "$CHANGELOG" | grep -q "### ✨ Features"; then
FEATURES=$(echo "$CHANGELOG" | sed -n '/### ✨ Features/,/^###/p' | grep "^- " || true)
fi
if echo "$CHANGELOG" | grep -q "### 🐛 Bug Fixes"; then
FIXES=$(echo "$CHANGELOG" | sed -n '/### 🐛 Bug Fixes/,/^###/p' | grep "^- " || true)
if [ -n "$FEATURES" ] && [ -n "$FIXES" ]; then
FEATURES="${FEATURES}"$'\n'"${FIXES}"
elif [ -n "$FIXES" ]; then
FEATURES="$FIXES"
fi
fi
# Trim leading/trailing whitespace and empty lines
FEATURES=$(echo "$FEATURES" | sed '/^$/d')
# If no features/fixes, use a generic message
if [ -z "$FEATURES" ]; then
FEATURES="- 小更新和改进"
fi
# Write replacement content to temp file (avoids sed multiline issues)
{
echo "## 🎉 最新功能 (v$VERSION)"
echo ""
echo "$FEATURES"
echo ""
echo "[📋 查看完整更新日志](CHANGE.md)"
} > /tmp/new_section_zh.txt
# Use awk for reliable multiline replacement
awk '
/^## 🎉 最新功能/ { skip=1; while((getline line < "/tmp/new_section_zh.txt") > 0) print line; close("/tmp/new_section_zh.txt") }
/^\[📋 查看完整更新日志\]/ { skip=0; next }
!skip { print }
' README_zh_cn.md > /tmp/README_zh_cn.md.new
mv /tmp/README_zh_cn.md.new README_zh_cn.md
echo "✅ Updated README_zh_cn.md with new features"
# Update CHANGE.md
- name: Update CHANGE.md
env:
REPO: ${{ github.repository }}
run: |
DATE=$(date +"%B %d %Y")
# Read the generated changelog
CHANGELOG=$(cat /tmp/changelog.txt)
# Convert changelog to CHANGE.md format
# For Core releases, use Core version. For Util-only, use Core version with note
if [ "${{ inputs.release_core }}" == "true" ]; then
VERSION="${{ needs.validate.outputs.core_version }}"
CHANGE_ENTRY="## $VERSION ($DATE)\n\n"
else
VERSION="${{ needs.validate.outputs.core_version }}"
CHANGE_ENTRY="## $VERSION ($DATE) - Util v${{ needs.validate.outputs.util_version }}\n\n"
fi
# Extract features and fixes from changelog (already contains PR hyperlinks)
# Format: "- Description (scope)" for conventional commits, "- Description" for others
if echo "$CHANGELOG" | grep -q "### ✨ Features"; then
FEATURES=$(echo "$CHANGELOG" | sed -n '/### ✨ Features/,/###/p' | grep "^- " || true)
if [ -n "$FEATURES" ]; then
while IFS= read -r line; do
# Convert **scope**: format to "Description (scope)" format
if [[ $line =~ ^\-\ \*\*([^*]+)\*\*:\ (.+)$ ]]; then
scope="${BASH_REMATCH[1]}"
desc="${BASH_REMATCH[2]}"
# Capitalize first letter of description
desc_cap="$(echo "${desc:0:1}" | tr '[:lower:]' '[:upper:]')${desc:1}"
CHANGE_ENTRY="${CHANGE_ENTRY}- ${desc_cap} (${scope})\n"
else
CHANGE_ENTRY="${CHANGE_ENTRY}${line}\n"
fi
done <<< "$FEATURES"
fi
fi
if echo "$CHANGELOG" | grep -q "### 🐛 Bug Fixes"; then
FIXES=$(echo "$CHANGELOG" | sed -n '/### 🐛 Bug Fixes/,/###/p' | grep "^- " || true)
if [ -n "$FIXES" ]; then
while IFS= read -r line; do
if [[ $line =~ ^\-\ \*\*([^*]+)\*\*:\ (.+)$ ]]; then
scope="${BASH_REMATCH[1]}"
desc="${BASH_REMATCH[2]}"
desc_cap="$(echo "${desc:0:1}" | tr '[:lower:]' '[:upper:]')${desc:1}"
CHANGE_ENTRY="${CHANGE_ENTRY}- ${desc_cap} (${scope})\n"
else
CHANGE_ENTRY="${CHANGE_ENTRY}${line}\n"
fi
done <<< "$FIXES"
fi
fi
# Extract other changes (non-conventional commits) - already contains PR hyperlinks
if echo "$CHANGELOG" | grep -q "### 📦 Other Changes"; then
OTHERS=$(echo "$CHANGELOG" | sed -n '/### 📦 Other Changes/,/###/p' | grep "^- " || true)
if [ -n "$OTHERS" ]; then
while IFS= read -r line; do
CHANGE_ENTRY="${CHANGE_ENTRY}${line}\n"
done <<< "$OTHERS"
fi
fi
# Add manual changelog entries
if [ -n "${{ inputs.manual_changelog }}" ]; then
CHANGE_ENTRY="${CHANGE_ENTRY}${{ inputs.manual_changelog }}\n"
fi
CHANGE_ENTRY="${CHANGE_ENTRY}\n"
# Prepend to CHANGE.md (after "## All Versions" line)
sed -i "2i\\$CHANGE_ENTRY" CHANGE.md
echo "✅ Updated CHANGE.md"
# Commit and push changes
- name: Commit and push changes
run: |
# Use GitHub's bot email format so the app avatar shows on commits
# The user ID (257041894) is from: gh api '/users/jengine-release-bot[bot]' --jq '.id'
# This is different from the App ID - it's the bot account's user ID
git config user.name "jengine-release-bot[bot]"
git config user.email "257041894+jengine-release-bot[bot]@users.noreply.github.com"
git add UnityProject/Packages/*/package.json README*.md CHANGE.md
# Different commit message based on what's being released
if [ "${{ inputs.release_core }}" == "true" ]; then
git commit -m "chore(release): ${{ needs.validate.outputs.release_tag }}"
else
git commit -m "chore(util): update to v${{ needs.validate.outputs.util_version }}"
fi
git push origin ${{ github.ref_name }}
echo "✅ Committed and pushed changes"
# Create Git tag (always - needed for OpenUPM detection)
- name: Create Git tag
run: |
git tag ${{ needs.validate.outputs.release_tag }}
git push origin ${{ needs.validate.outputs.release_tag }}
echo "✅ Created and pushed tag ${{ needs.validate.outputs.release_tag }}"
# Summary
- name: Release Summary
run: |
echo "## 📦 Package Update Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${{ inputs.release_core }}" == "true" ]; then
echo "✅ **JEngine.Core**: v${{ needs.validate.outputs.core_version }}" >> $GITHUB_STEP_SUMMARY
fi
if [ "${{ inputs.release_util }}" == "true" ]; then
echo "✅ **JEngine.Util**: v${{ needs.validate.outputs.util_version }}" >> $GITHUB_STEP_SUMMARY
fi
if [ "${{ inputs.release_ui }}" == "true" ]; then
echo "✅ **JEngine.UI**: v${{ needs.validate.outputs.ui_version }}" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "🏷️ **Git Tag**: ${{ needs.validate.outputs.release_tag }}" >> $GITHUB_STEP_SUMMARY
if [ "${{ needs.validate.outputs.create_github_release }}" == "true" ]; then
echo "📋 **GitHub Release**: Will be created" >> $GITHUB_STEP_SUMMARY
else
echo "ℹ️ **GitHub Release**: Not created (non-Core update)" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "📦 **OpenUPM**: Will detect update from git tag \`${{ needs.validate.outputs.release_tag }}\`" >> $GITHUB_STEP_SUMMARY
create-release:
name: Create GitHub Release
needs: [validate, prepare-release]
if: needs.validate.outputs.create_github_release == 'true'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
# Generate GitHub App token so the release is created by JEngine Release Bot
- name: Generate GitHub App Token
id: generate-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.RELEASE_APP_ID }}
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ needs.validate.outputs.release_tag }}
- name: Create GitHub Release
env:
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
run: |
# Create release body
cat > /tmp/release_body.md << 'RELEASE_EOF'
${{ needs.prepare-release.outputs.changelog }}
---
## 📦 Installation
Install via [OpenUPM](https://openupm.com/):
```bash
openupm add com.jasonxudeveloper.jengine.core
openupm add com.jasonxudeveloper.jengine.util
openupm add com.jasonxudeveloper.jengine.ui # Optional: UI utilities
```
## 📖 Documentation
- [English Documentation](https://jengine.xgamedev.net/)
- [中文文档](https://jengine.xgamedev.net/zh/)
---
*This release was automatically created by the JEngine Release Bot*
RELEASE_EOF
# Create the release using gh CLI (no deprecated set-output)
gh release create "${{ needs.validate.outputs.release_tag }}" \
--title "v${{ needs.validate.outputs.release_tag }}" \
--notes-file /tmp/release_body.md
- name: Summary
run: |
echo "## 🎉 Release Created Successfully!" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Tag**: ${{ needs.validate.outputs.release_tag }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${{ inputs.release_core }}" == "true" ]; then
echo "✅ **JEngine.Core**: v${{ needs.validate.outputs.core_version }}" >> $GITHUB_STEP_SUMMARY
fi
if [ "${{ inputs.release_util }}" == "true" ]; then
echo "✅ **JEngine.Util**: v${{ needs.validate.outputs.util_version }}" >> $GITHUB_STEP_SUMMARY
fi
if [ "${{ inputs.release_ui }}" == "true" ]; then
echo "✅ **JEngine.UI**: v${{ needs.validate.outputs.ui_version }}" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "**OpenUPM will automatically detect and build the packages within 10-15 minutes.**" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "📋 [View Release](https://github.com/${{ github.repository }}/releases/tag/${{ needs.validate.outputs.release_tag }})" >> $GITHUB_STEP_SUMMARY