Skip to content

Merge pull request #21 from IvanMurzak/refactor/package-layout #136

Merge pull request #21 from IvanMurzak/refactor/package-layout

Merge pull request #21 from IvanMurzak/refactor/package-layout #136

Workflow file for this run

name: release
on:
workflow_dispatch:
push:
branches:
- main
env:
INSTALLER_UNITY_VERSION: 2022.3.62f3
jobs:
check-version-tag:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.get_version.outputs.current-version }}
prev_tag: ${{ steps.prev_tag.outputs.tag }}
tag_exists: ${{ steps.tag_exists.outputs.exists }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0
fetch-tags: true
- name: Get version from package.json
id: get_version
uses: martinbeentjes/npm-get-version-action@v1.3.1
with:
path: Unity-Package/Packages/com.ivanmurzak.unity.mcp.particlesystem
- name: Find previous version tag
id: prev_tag
uses: WyriHaximus/github-action-get-previous-tag@v2
- name: Check if tag exists
id: tag_exists
uses: mukunku/tag-exists-action@v1.7.0
with:
tag: ${{ steps.get_version.outputs.current-version }}
# Generates release notes (release.md) in parallel with tests/builds.
# The artifact is consumed by release-unity-plugin's atomic publish step.
prepare-release-notes:
runs-on: ubuntu-latest
needs: [check-version-tag]
if: needs.check-version-tag.outputs.tag_exists == 'false'
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0
fetch-tags: true
- name: Generate release description
env:
GH_TOKEN: ${{ github.token }}
run: |
set -e
version=${{ needs.check-version-tag.outputs.version }}
prev_tag=${{ needs.check-version-tag.outputs.prev_tag }}
repo_url="https://github.com/${GITHUB_REPOSITORY}"
today=$(date +'%B %e, %Y')
echo "repo_url: $repo_url"
echo "today: $today"
echo "# Package $version" > release.md
echo "**Released:** *$today*" >> release.md
echo "" >> release.md
echo "---" >> release.md
echo "" >> release.md
if [ -n "$prev_tag" ]; then
echo "## Comparison" >> release.md
echo "See every change: [Compare $prev_tag...$version]($repo_url/compare/$prev_tag...$version)" >> release.md
echo "" >> release.md
echo "---" >> release.md
echo "" >> release.md
echo "## Commit Summary (Newest → Oldest)" >> release.md
for sha in $(git log --pretty=format:'%H' $prev_tag..HEAD); do
username=$(gh api repos/${GITHUB_REPOSITORY}/commits/$sha --jq '.author.login // .commit.author.name' 2>/dev/null || true)
if [ -z "$username" ]; then
username=$(git log -1 --pretty=format:'%an' $sha)
fi
message=$(git log -1 --pretty=format:'%s' $sha)
short_sha=$(git log -1 --pretty=format:'%h' $sha)
echo "- [\`$short_sha\`]($repo_url/commit/$sha) — $message by @$username" >> release.md
done
fi
- name: Upload release notes as artifact
uses: actions/upload-artifact@v6
with:
name: release-notes
path: ./release.md
build-unity-installer:
runs-on: ubuntu-latest
needs: [check-version-tag]
if: needs.check-version-tag.outputs.tag_exists == 'false'
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Free disk space
uses: jlumbroso/free-disk-space@v1.3.1
with:
tool-cache: false
android: true
dotnet: true
haskell: true
large-packages: true
swap-storage: true
- name: Cache Unity Library
uses: actions/cache@v5
with:
path: ./Installer/Library
key: Library-Unity-Installer
- name: Test Unity Installer (EditMode)
uses: game-ci/unity-test-runner@v4
env:
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
with:
projectPath: ./Installer
unityVersion: ${{ env.INSTALLER_UNITY_VERSION }}
customImage: "unityci/editor:ubuntu-${{ env.INSTALLER_UNITY_VERSION }}-base-3"
testMode: editmode
githubToken: ${{ secrets.GITHUB_TOKEN }}
checkName: Unity Installer EditMode Test Results
artifactsPath: artifacts-installer-editmode
customParameters: -CI true -GITHUB_ACTIONS true
- name: Clean Unity artifacts and reset git state
run: |
# Force remove Unity generated files with restricted permissions
sudo rm -rf ./Installer/Logs/ || true
sudo rm -rf ./Installer/Temp/ || true
sudo rm -rf ./artifacts-installer-editmode/ || true
# Reset only tracked files to their committed state
git reset --hard HEAD
echo "Cleaned Unity artifacts and reset tracked files"
- name: Export Unity Package
uses: game-ci/unity-builder@v5
env:
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
with:
projectPath: ./Installer
unityVersion: ${{ env.INSTALLER_UNITY_VERSION }}
customImage: "unityci/editor:ubuntu-${{ env.INSTALLER_UNITY_VERSION }}-base-3"
buildName: AI-ParticleSystem-Installer
buildsPath: build
buildMethod: com.IvanMurzak.Unity.MCP.ParticleSystem.Installer.PackageExporter.ExportPackage
customParameters: -CI true -GITHUB_ACTIONS true
- name: Upload Unity Package as artifact
uses: actions/upload-artifact@v6
with:
name: unity-installer-package
path: ./Installer/build/AI-ParticleSystem-Installer.unitypackage
# Builds the signed UPM package (.tgz with package/.attestation.p7m) in parallel
# with tests/builds and uploads it as a `signed-upm-package` artifact for the
# atomic release publish in release-unity-plugin.
#
# HARD-GATE: this job is NOT continue-on-error. Missing UPM signing secrets fail
# fast and the release pipeline halts — no GitHub Release is created without a
# signed UPM tarball. See docs/openupm-signing.md for the blocking-semantics
# rationale.
#
# Required repo secrets (configure via `gh secret set --repo IvanMurzak/Unity-AI-ParticleSystem <NAME>`):
# - UPM_SERVICE_ACCOUNT_KEY_ID
# - UPM_SERVICE_ACCOUNT_KEY_SECRET
# - UPM_ORG_ID
# See https://openupm.com/docs/signing-upm-packages.html for the procedure.
build-signed-upm-package:
runs-on: ubuntu-latest
needs: [check-version-tag]
if: needs.check-version-tag.outputs.tag_exists == 'false'
env:
UPM_SERVICE_ACCOUNT_KEY_ID: ${{ secrets.UPM_SERVICE_ACCOUNT_KEY_ID }}
UPM_SERVICE_ACCOUNT_KEY_SECRET: ${{ secrets.UPM_SERVICE_ACCOUNT_KEY_SECRET }}
UPM_ORG_ID: ${{ secrets.UPM_ORG_ID }}
PACKAGE_DIR: Unity-Package/Packages/com.ivanmurzak.unity.mcp.particlesystem
DIST_DIR: /tmp/signed-upm-dist
steps:
- name: Verify signing secrets are configured
run: |
missing=()
[ -z "$UPM_SERVICE_ACCOUNT_KEY_ID" ] && missing+=("UPM_SERVICE_ACCOUNT_KEY_ID")
[ -z "$UPM_SERVICE_ACCOUNT_KEY_SECRET" ] && missing+=("UPM_SERVICE_ACCOUNT_KEY_SECRET")
[ -z "$UPM_ORG_ID" ] && missing+=("UPM_ORG_ID")
if [ "${#missing[@]}" -ne 0 ]; then
printf '::error::UPM signing secrets are not configured (%s). The release pipeline is hard-gated on signing — see docs/openupm-signing.md for setup.\n' "${missing[*]}"
exit 1
fi
- name: Checkout repository
uses: actions/checkout@v6
- name: Log package metadata
run: |
package_name="$(jq -r '.name' "$PACKAGE_DIR/package.json")"
package_version="$(jq -r '.version' "$PACKAGE_DIR/package.json")"
printf 'Package name: %s\n' "$package_name"
printf 'Package version: %s\n' "$package_version"
- name: Install Unity UPM CLI
run: |
curl -fsSL https://cdn.packages.unity.com/upm-cli/install.sh -o install.sh
bash install.sh
echo "$HOME/.upm/bin" >> "$GITHUB_PATH"
- name: Verify Unity UPM CLI
run: upm --help
- name: Sign package
run: |
mkdir -p "$DIST_DIR"
upm pack "./$PACKAGE_DIR" --organization-id "$UPM_ORG_ID" --destination "$DIST_DIR"
- name: Verify signed package contains attestation
run: |
shopt -s nullglob
archives=("$DIST_DIR"/*.tgz "$DIST_DIR"/*.tar.gz)
if [ "${#archives[@]}" -ne 1 ]; then
printf 'Expected exactly one signed package archive, found %s: %s\n' "${#archives[@]}" "${archives[*]:-<none>}" >&2
exit 1
fi
archive="${archives[0]}"
archive_basename="$(basename "$archive")"
# OpenUPM consumes the asset via the `githubReleaseAssetName: 'com.ivanmurzak.unity.mcp.particlesystem-'`
# prefix documented in docs/openupm-signing.md. Enforce the contract here so a future
# `upm pack` naming change fails CI loudly instead of silently breaking OpenUPM pickup.
if [[ "$archive_basename" != com.ivanmurzak.unity.mcp.particlesystem-* ]]; then
printf 'Signed archive basename %q does not begin with the OpenUPM-expected prefix com.ivanmurzak.unity.mcp.particlesystem- (see docs/openupm-signing.md)\n' "$archive_basename" >&2
exit 1
fi
archive_entries="$(tar -tzf "$archive")"
grep -qx 'package/package.json' <<<"$archive_entries"
grep -qx 'package/.attestation.p7m' <<<"$archive_entries"
printf 'Signed archive: %s\n' "$archive_basename"
tar -xOzf "$archive" package/package.json | jq '{name, version}'
- name: Upload signed UPM package as artifact
uses: actions/upload-artifact@v6
with:
name: signed-upm-package
path: /tmp/signed-upm-dist/*.tgz
# --- UNITY TESTS ---
# -------------------
# --- EDIT MODE ---
test-unity-2022-3-62f3-editmode:
needs: [build-unity-installer]
uses: ./.github/workflows/test_unity_plugin.yml
with:
projectPath: "./Unity-Tests/2022.3.62f3"
unityVersion: "2022.3.62f3"
testMode: "editmode"
secrets: inherit
test-unity-2023-2-22f1-editmode:
needs: [build-unity-installer]
uses: ./.github/workflows/test_unity_plugin.yml
with:
projectPath: "./Unity-Tests/2023.2.22f1"
unityVersion: "2023.2.22f1"
testMode: "editmode"
secrets: inherit
test-unity-6000-3-1f1-editmode:
needs: [build-unity-installer]
uses: ./.github/workflows/test_unity_plugin.yml
with:
projectPath: "./Unity-Tests/6000.3.1f1"
unityVersion: "6000.3.1f1"
testMode: "editmode"
secrets: inherit
# --- PLAY MODE ---
test-unity-2022-3-62f3-playmode:
needs: [build-unity-installer]
uses: ./.github/workflows/test_unity_plugin.yml
with:
projectPath: "./Unity-Tests/2022.3.62f3"
unityVersion: "2022.3.62f3"
testMode: "playmode"
secrets: inherit
test-unity-2023-2-22f1-playmode:
needs: [build-unity-installer]
uses: ./.github/workflows/test_unity_plugin.yml
with:
projectPath: "./Unity-Tests/2023.2.22f1"
unityVersion: "2023.2.22f1"
testMode: "playmode"
secrets: inherit
test-unity-6000-3-1f1-playmode:
needs: [build-unity-installer]
uses: ./.github/workflows/test_unity_plugin.yml
with:
projectPath: "./Unity-Tests/6000.3.1f1"
unityVersion: "6000.3.1f1"
testMode: "playmode"
secrets: inherit
# --- STANDALONE ---
test-unity-2022-3-62f3-standalone:
needs: [build-unity-installer]
uses: ./.github/workflows/test_unity_plugin.yml
with:
projectPath: "./Unity-Tests/2022.3.62f3"
unityVersion: "2022.3.62f3"
testMode: "standalone"
secrets: inherit
test-unity-2023-2-22f1-standalone:
needs: [build-unity-installer]
uses: ./.github/workflows/test_unity_plugin.yml
with:
projectPath: "./Unity-Tests/2023.2.22f1"
unityVersion: "2023.2.22f1"
testMode: "standalone"
secrets: inherit
test-unity-6000-3-1f1-standalone:
needs: [build-unity-installer]
uses: ./.github/workflows/test_unity_plugin.yml
with:
projectPath: "./Unity-Tests/6000.3.1f1"
unityVersion: "6000.3.1f1"
testMode: "standalone"
secrets: inherit
# -------------------
# Atomic publish point — gated on EVERY prerequisite (tests, installer build,
# signed UPM package, release notes). Downloads all asset artifacts and creates
# the GitHub Release + tag with the full asset set in a SINGLE
# softprops/action-gh-release@v2 call so a failed upload cannot strand the
# release with incomplete assets. Signing failure → no release (hard gate).
release-unity-plugin:
runs-on: ubuntu-latest
needs:
[
check-version-tag,
prepare-release-notes,
build-unity-installer,
build-signed-upm-package,
test-unity-2022-3-62f3-editmode,
test-unity-2022-3-62f3-playmode,
test-unity-2022-3-62f3-standalone,
test-unity-2023-2-22f1-editmode,
test-unity-2023-2-22f1-playmode,
test-unity-2023-2-22f1-standalone,
test-unity-6000-3-1f1-editmode,
test-unity-6000-3-1f1-playmode,
test-unity-6000-3-1f1-standalone,
]
if: needs.check-version-tag.outputs.tag_exists == 'false'
outputs:
version: ${{ needs.check-version-tag.outputs.version }}
steps:
- name: Download release notes artifact
uses: actions/download-artifact@v6
with:
name: release-notes
path: ./release-notes
- name: Download Unity installer artifact
uses: actions/download-artifact@v6
with:
name: unity-installer-package
path: ./assets
- name: Download signed UPM package artifact
uses: actions/download-artifact@v6
with:
name: signed-upm-package
path: ./assets
- name: List assembled release assets
run: |
set -e
echo "Release notes:"
ls -la ./release-notes
echo ""
echo "Release assets:"
ls -la ./assets
- name: Create Tag and Release with all assets
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ needs.check-version-tag.outputs.version }}
name: ${{ needs.check-version-tag.outputs.version }}
body_path: ./release-notes/release.md
draft: false
prerelease: false
fail_on_unmatched_files: true
files: |
./assets/AI-ParticleSystem-Installer.unitypackage
./assets/com.ivanmurzak.unity.mcp.particlesystem-*.tgz
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Cleanup job to remove build artifacts after the atomic publish.
cleanup-artifacts:
runs-on: ubuntu-latest
needs: [release-unity-plugin]
if: always()
steps:
- name: Delete Unity Package artifacts
uses: geekyeggo/delete-artifact@v5
with:
name: unity-installer-package
failOnError: false
continue-on-error: true
- name: Delete signed UPM package artifacts
uses: geekyeggo/delete-artifact@v5
with:
name: signed-upm-package
failOnError: false
continue-on-error: true
- name: Delete release notes artifacts
uses: geekyeggo/delete-artifact@v5
with:
name: release-notes
failOnError: false
continue-on-error: true