Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 12 additions & 13 deletions .github/workflows/development-buildandtestupmrelease.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,17 @@ on:
pull_request:
branches-ignore:
- 'main'
# Ignore PRs targeting main

# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:

concurrency:
group: ${{ github.ref }}
cancel-in-progress: true

# Ensure default token scopes and inherit org-level secrets via env mapping
permissions:
contents: write
packages: read

env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GIT_PAT: ${{ secrets.GIT_PAT }}

jobs:
test-unity-build:
name: Test Unity UPM Build
Expand All @@ -30,15 +23,21 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
# os: [ubuntu-latest, windows-latest, macos-latest]
os: [windows-latest, macos-latest]
unity-version:
- 6000.0.x
- 6000
- '6000.0.x'
- '6000.1'
- '6000.2'
- '6000.3'
- '6000.5'
- '6000.6'
- '6000.7'
include:
- os: ubuntu-latest
build-targets: StandaloneLinux64, Android
# - os: ubuntu-latest
# build-targets: StandaloneLinux64, Android
- os: windows-latest
build-targets: StandaloneWindows64
build-targets: StandaloneWindows64, Android
- os: macos-latest
build-targets: StandaloneOSX, iOS
steps:
Expand Down
51 changes: 51 additions & 0 deletions .github/workflows/getpackageversionfrompackage.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Get the package version from a UPM package.json file

on:
workflow_call:
inputs:
build-host:
required: true
type: string
target-branch:
description: Branch to read package.json from. Defaults to the triggering ref.
required: false
type: string
default: ${{ github.ref }}
version-file-path:
description: Optional path to the package.json to read. Defaults to package.json in the repo root.
required: false
type: string
default: package.json
outputs:
packageversion:
description: The version field of the UPM package
value: ${{ jobs.get_package_version.outputs.upmpackageversion }}

jobs:
get_package_version:
name: Get package version from UPM package
runs-on: ${{ inputs.build-host }}
outputs:
upmpackageversion: ${{ steps.getVersion.outputs.packageversion }}
steps:
- uses: actions/checkout@v7
with:
ref: ${{ inputs.target-branch }}

- id: getVersion
name: Read package version
shell: bash
env:
VERSION_FILE: ${{ inputs.version-file-path }}
run: |
if [ ! -f "$VERSION_FILE" ]; then
echo "::error::No package.json found at $VERSION_FILE"
exit 1
fi
version=$(jq -r '.version // empty' "$VERSION_FILE")
if [ -z "$version" ]; then
echo "::error::package.json at $VERSION_FILE has no version"
exit 1
fi
echo "Detected package version $version"
echo "packageversion=$version" >> "$GITHUB_OUTPUT"
115 changes: 115 additions & 0 deletions .github/workflows/main-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
name: Publish main branch and increment version

# Runs when a pull request into main is merged. The PR title selects the release type:
# - contains "no-ver" - tag the version already in package.json, no bump
# - contains "major-release" - bump major (1.x.x -> 2.0.0)
# - contains "minor-release" - bump minor (1.0.x -> 1.1.0)
# - anything else - patch release: strip the pre-release suffix (1.0.0-pre.1 -> 1.0.0),
# or bump patch if there is no suffix (1.0.0 -> 1.0.1)
#
# After tagging, development is refreshed from main and moved to the next pre-release (1.0.1-pre.1).
#
# All commits and tags are pushed by the ui-extensions-bot GitHub App using the org secrets
# RELEASE_APP_CLIENT_ID and RELEASE_APP_PRIVATE_KEY. No personal access token is involved.

on:
pull_request:
types:
- closed
branches:
- main

permissions:
contents: read

concurrency:
group: release-${{ github.event.pull_request.base.ref }}
cancel-in-progress: false

jobs:
# Read the version to tag when the PR title contains "no-ver" (no version bump)
validate-environment:
if: github.event.pull_request.merged == true && contains(github.event.pull_request.title, 'no-ver')
name: Get version from UPM package
uses: ./.github/workflows/getpackageversionfrompackage.yml
with:
build-host: ubuntu-latest
target-branch: ${{ github.event.pull_request.base.ref }}

release-package-only:
needs: validate-environment
name: Release package only, no upversion
uses: ./.github/workflows/tagrelease.yml
with:
build-host: ubuntu-latest
target-branch: ${{ github.event.pull_request.base.ref }}
version: ${{ needs.validate-environment.outputs.packageversion }}
secrets: inherit

upversion-major-package:
if: github.event.pull_request.merged == true && contains(github.event.pull_request.title, 'no-ver') == false && contains(github.event.pull_request.title, 'major-release')
name: Major version package and release
uses: ./.github/workflows/upversionandtagrelease.yml
with:
build-host: ubuntu-latest
build-type: major
target-branch: ${{ github.event.pull_request.base.ref }}
secrets: inherit

upversion-minor-package:
if: github.event.pull_request.merged == true && contains(github.event.pull_request.title, 'no-ver') == false && contains(github.event.pull_request.title, 'minor-release')
name: Minor version package and release
uses: ./.github/workflows/upversionandtagrelease.yml
with:
build-host: ubuntu-latest
build-type: minor
target-branch: ${{ github.event.pull_request.base.ref }}
secrets: inherit

# Default path when no release keyword is in the PR title
upversion-patch-package:
if: github.event.pull_request.merged == true && contains(github.event.pull_request.title, 'no-ver') == false && contains(github.event.pull_request.title, 'minor-release') == false && contains(github.event.pull_request.title, 'major-release') == false
name: Patch version package and release
uses: ./.github/workflows/upversionandtagrelease.yml
with:
build-host: ubuntu-latest
build-type: patch-release
target-branch: ${{ github.event.pull_request.base.ref }}
secrets: inherit

release-complete:
# Runs only for a merged PR and only if no release job failed. The release jobs that did not
# match the PR title are skipped, which is fine. A real failure skips this job so the
# development refresh never runs from a half-finished release.
if: ${{ github.event.pull_request.merged == true && !failure() && !cancelled() }}
needs: [upversion-major-package, upversion-minor-package, upversion-patch-package, release-package-only]
name: Release complete
runs-on: ubuntu-latest
steps:
- name: Release done
run: echo "Release done, refreshing development"

# Merge the released main branch back into development
refresh-development:
if: ${{ needs.release-complete.result == 'success' }}
needs: [release-complete]
name: Refresh development branch
uses: ./.github/workflows/refreshbranch.yml
with:
build-host: ubuntu-latest
target-branch: development
source-branch: ${{ github.event.pull_request.base.ref }}
secrets: inherit

# Move development to the next pre-release version, no tag
upversion-development:
if: ${{ needs.refresh-development.result == 'success' }}
needs: [refresh-development]
name: Upversion the development branch for the next release
uses: ./.github/workflows/upversionandtagrelease.yml
with:
build-host: ubuntu-latest
build-type: patch-pre
target-branch: development
createTag: false
secrets: inherit
71 changes: 71 additions & 0 deletions .github/workflows/refreshbranch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
name: Refresh branch

# Merges one branch into another and pushes the result. Used after a release to bring
# the version bump on main back into development.

on:
workflow_call:
inputs:
build-host:
required: true
type: string
target-branch:
description: Branch that receives the merge, for example development
required: true
type: string
source-branch:
description: Branch merged into the target, for example main
required: true
type: string
secrets:
RELEASE_APP_CLIENT_ID:
required: true
RELEASE_APP_PRIVATE_KEY:
required: true

jobs:
refreshBranch:
name: Refresh ${{ inputs.target-branch }} from ${{ inputs.source-branch }}
runs-on: ${{ inputs.build-host }}
steps:
- name: Create release App token
uses: actions/create-github-app-token@v3
id: app-token
with:
client-id: ${{ secrets.RELEASE_APP_CLIENT_ID }}
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}

- name: Get App bot user id
id: bot
shell: bash
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
APP_SLUG: ${{ steps.app-token.outputs.app-slug }}
run: echo "id=$(gh api "/users/${APP_SLUG}[bot]" --jq .id)" >> "$GITHUB_OUTPUT"

- uses: actions/checkout@v7
with:
ref: ${{ inputs.target-branch }}
fetch-depth: 0
clean: true
token: ${{ steps.app-token.outputs.token }}

- name: Configure git identity
shell: bash
env:
APP_SLUG: ${{ steps.app-token.outputs.app-slug }}
BOT_ID: ${{ steps.bot.outputs.id }}
run: |
git config --global user.name "${APP_SLUG}[bot]"
git config --global user.email "${BOT_ID}+${APP_SLUG}[bot]@users.noreply.github.com"

- name: Merge source branch into target branch
shell: bash
env:
TARGET_BRANCH: ${{ inputs.target-branch }}
SOURCE_BRANCH: ${{ inputs.source-branch }}
run: |
git fetch origin "$SOURCE_BRANCH"
git merge --no-edit -m "Refresh $TARGET_BRANCH from $SOURCE_BRANCH [skip ci]" FETCH_HEAD
git push origin "HEAD:$TARGET_BRANCH"
echo "Branch $TARGET_BRANCH updated with changes from $SOURCE_BRANCH"
94 changes: 94 additions & 0 deletions .github/workflows/release-preflight.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
name: Release preflight

# Runs on pull requests into main (the release PRs) and on demand. It publishes nothing.
# It confirms the ui-extensions-bot GitHub App can mint a token with push access to this repo,
# and reports the version main-publish will produce when the PR is merged.

on:
pull_request:
branches:
- main
workflow_dispatch:

permissions:
contents: read

jobs:
validate-release-app:
name: Validate release App and version
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
# Do not store the workflow token in the repo config. The push probe below must send only the App token.
persist-credentials: false

- name: Create release App token
uses: actions/create-github-app-token@v3
id: app-token
with:
client-id: ${{ secrets.RELEASE_APP_CLIENT_ID }}
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}

- name: Check the App can push to this repo
shell: bash
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
APP_SLUG: ${{ steps.app-token.outputs.app-slug }}
TARGET_BRANCH: ${{ github.event.pull_request.base.ref || github.ref_name }}
run: |
botid=$(gh api "/users/${APP_SLUG}[bot]" --jq .id)
echo "App $APP_SLUG resolves to bot user id $botid"
echo "Repository permissions block as seen by the token (all false for an installation token, not meaningful): $(gh api "repos/$GITHUB_REPOSITORY" --jq '.permissions // "absent"')"

# The repository object's permissions block describes a user, so it cannot test an App token.
# A dry-run push exercises write access on the server without writing anything. It targets a
# branch name that does not exist, because the runner has a shallow checkout and a push to an
# existing branch fails the client-side fast-forward check before proving anything. The empty
# extraheader clears any Authorization header left in the repo config so only the App token is sent.
auth=$(printf 'x-access-token:%s' "$GH_TOKEN" | base64 -w 0)
probe_ref="refs/heads/preflight-probe-$GITHUB_RUN_ID"
if output=$(git -c "http.https://github.com/.extraheader=" -c "http.https://github.com/.extraheader=AUTHORIZATION: basic $auth" push --dry-run "https://github.com/$GITHUB_REPOSITORY.git" "HEAD:$probe_ref" 2>&1); then
echo "$output"
echo "App $APP_SLUG can push to $GITHUB_REPOSITORY. Releases will be pushed to $TARGET_BRANCH."
else
echo "$output"
if echo "$output" | grep -qE "403|not granted|Permission"; then
echo "::error::GitHub refused write access for App $APP_SLUG on $GITHUB_REPOSITORY. Check the App installation covers this repository and has Contents read and write."
else
echo "::error::The push probe failed for a reason other than permissions. See the git output above."
fi
exit 1
fi

- name: Report the version this PR will release
shell: bash
env:
PR_TITLE: ${{ github.event.pull_request.title }}
run: |
version=$(jq -r .version package.json)
base="${version%%-*}"
IFS=. read -r major minor patch <<< "$base"
case "$PR_TITLE" in
*no-ver*) kind="no bump"; next="$version" ;;
*major-release*) kind="major bump"; next="$((major + 1)).0.0" ;;
*minor-release*) kind="minor bump"; next="$major.$((minor + 1)).0" ;;
*)
kind="patch release"
if [ "$base" != "$version" ]; then next="$base"; else next="$major.$minor.$((patch + 1))"; fi
;;
esac
echo "package.json version: $version"
echo "PR title: $PR_TITLE"
echo "On merge, main-publish will do a $kind and tag v$next"
if git ls-remote --exit-code --tags origin "refs/tags/v$next" > /dev/null 2>&1; then
echo "::error::Tag v$next already exists. The release would fail at the tag step."
exit 1
fi
{
echo "## Release preflight"
echo ""
echo "- package.json version: \`$version\`"
echo "- release type: $kind"
echo "- tag on merge: \`v$next\`"
} >> "$GITHUB_STEP_SUMMARY"
Loading