Skip to content

Release

Release #17

Workflow file for this run

name: Release
on:
workflow_dispatch:
inputs:
bump_level:
description: "Version increment level"
type: choice
options:
- patch
- minor
- major
default: minor
workflow_run:
branches: [master]
workflows: ["Tests"]
types: [completed]
permissions:
contents: write
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: true
jobs:
release:
if: >
(github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') ||
github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
env:
PACKAGE_JSON: package.json
steps:
- name: Resolve target
id: target
shell: bash
run: |
if [[ "${{ github.event_name }}" == "workflow_run" ]]; then
echo "ref=${{ github.event.workflow_run.head_branch }}" >> "$GITHUB_OUTPUT"
echo "sha=${{ github.event.workflow_run.head_sha }}" >> "$GITHUB_OUTPUT"
else
echo "ref=${{ github.ref_name }}" >> "$GITHUB_OUTPUT"
echo "sha=" >> "$GITHUB_OUTPUT"
fi
- uses: actions/checkout@v4
with:
ref: ${{ steps.target.outputs.sha || steps.target.outputs.ref }}
fetch-depth: 0
- name: Establish baseline
id: base
shell: bash
run: |
if [[ -n "${{ steps.target.outputs.sha }}" ]]; then
echo "sha=${{ steps.target.outputs.sha }}" >> "$GITHUB_OUTPUT"
else
echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
fi
echo "ref=${{ steps.target.outputs.ref }}" >> "$GITHUB_OUTPUT"
- name: Cancel if branch advanced
id: guard
shell: bash
run: |
ref="${{ steps.base.outputs.ref }}"
git fetch origin "$ref" --quiet
remote_sha="$(git rev-parse "origin/$ref")"
base_sha="${{ steps.base.outputs.sha }}"
if [[ "$remote_sha" != "$base_sha" ]]; then
echo "::notice::Remote branch '$ref' advanced from $base_sha to $remote_sha; cancelling release."
echo "changed=true" >> "$GITHUB_OUTPUT"
else
echo "changed=false" >> "$GITHUB_OUTPUT"
fi
- name: Skipping release due to new commits
if: steps.guard.outputs.changed == 'true'
shell: bash
run: |
{
echo "### Release skipped"
echo
echo "The branch has advanced after the workflow was started."
} >> "$GITHUB_STEP_SUMMARY"
- name: Get previous tag
if: steps.guard.outputs.changed == 'false'
id: prev
shell: bash
run: |
prev_tag="$(git describe --tags --abbrev=0 2>/dev/null || true)"
echo "tag=$prev_tag" >> "$GITHUB_OUTPUT"
- name: Read bump level
if: steps.guard.outputs.changed == 'false'
id: bump
shell: bash
run: |
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
echo "level=${{ inputs.bump_level }}" >> "$GITHUB_OUTPUT"
else
echo "level=patch" >> "$GITHUB_OUTPUT"
fi
echo "Selected bump level: ${{ steps.bump.outputs.level }}"
- name: Bump package.json
if: steps.guard.outputs.changed == 'false'
id: ver
shell: bash
run: |
set -euo pipefail
if [ ! -f "$PACKAGE_JSON" ]; then
echo "Package file not found at: $PACKAGE_JSON"
exit 1
fi
current="$(jq -r '.version' "$PACKAGE_JSON")"
if [[ ! "$current" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Version '$current' is not SemVer (x.y.z)"
exit 1
fi
IFS='.' read -r major minor patch <<<"$current"
level="${{ steps.bump.outputs.level }}"
case "$level" in
major)
major=$((major+1)); minor=0; patch=0
;;
minor)
minor=$((minor+1)); patch=0
;;
patch)
patch=$((patch+1))
;;
*)
echo "Unsupported bump level: '$level' (expected: major|minor|patch)"
exit 1
;;
esac
new="$major.$minor.$patch"
if [[ "$new" == "$current" ]]; then
echo "Version unchanged; nothing to do."
exit 0
fi
tmp="$PACKAGE_JSON.tmp"
jq --arg v "$new" '.version=$v' "$PACKAGE_JSON" > "$tmp"
mv "$tmp" "$PACKAGE_JSON"
echo "old=$current" >> "$GITHUB_OUTPUT"
echo "new=$new" >> "$GITHUB_OUTPUT"
- name: Commit and tag
if: steps.guard.outputs.changed == 'false' && steps.ver.outputs.new != ''
id: push
shell: bash
run: |
set -euo pipefail
echo "pushed=false" >> "$GITHUB_OUTPUT"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add "$PACKAGE_JSON"
git commit -m "${{ github.event.repository.name }} ${{ steps.ver.outputs.new }} [release] [skip ci]"
ref="${{ steps.base.outputs.ref }}"
base_sha="${{ steps.base.outputs.sha }}"
git fetch origin "$ref" --quiet
remote_sha="$(git rev-parse "origin/$ref")"
if [[ "$remote_sha" != "$base_sha" ]]; then
echo "::notice::Remote branch '$ref' advanced to $remote_sha; cancelling release."
exit 0
fi
if ! git push origin "HEAD:${{ steps.target.outputs.ref }}"; then
echo "::notice::Non-fast-forward push rejected; cancelling release."
exit 0
fi
git tag "v${{ steps.ver.outputs.new }}"
git push origin "v${{ steps.ver.outputs.new }}"
echo "pushed=true" >> "$GITHUB_OUTPUT"
echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
- name: Create or update latest release
if: steps.ver.outputs.new != '' && steps.push.outputs.pushed == 'true'
env:
GH_TOKEN: ${{ github.token }}
shell: bash
run: |
set -euo pipefail
tag="latest"
upper="${{ steps.push.outputs.sha }}"
declare -A since=()
prev_tag="${{ steps.prev.outputs.tag }}"
if [[ -n "${prev_tag:-}" ]]; then
while read -r s; do
[[ -n "$s" ]] && since["$s"]=1
done < <(git rev-list "${prev_tag}..$upper")
fi
notes="release-notes.md"
date_utc="$(date -u '+%d-%m-%Y')"
cat > "$notes" <<'MD'
This is a rolling release. Whenever all tests pass, it is automatically updated
to point to the latest release tag.
Installation
--------------
Compatibility: ***Unity 2022.3.12f1 or newer***
Recommended method:
- Window ⟶ Package Manager ⟶ `+` ⟶ "Install package from git URL"
- Paste the URL (the `release` branch which points at the latest commit with all tests passing)
```md
https://github.com/apkd/Medicine.git#release
```
Latest changes
--------------
| Author | Release | Commit | SHA |
|---|---|---|---|
MD
limit=12
count=0
mapfile -t rel_tags < <(git for-each-ref --sort=creatordate --format='%(refname:short)' refs/tags | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' || true)
git log -n 24 "$upper" --pretty=format:%H%x09%s%x09%an%x09%ae | while IFS=$'\t' read -r sha msg author email; do
lc_author="$(printf '%s' "$author" | tr '[:upper:]' '[:lower:]')"
lc_email="$(printf '%s' "$email" | tr '[:upper:]' '[:lower:]')"
if [[ "$lc_author" == "github-actions[bot]" || "$lc_email" == "41898282+github-actions[bot]@users.noreply.github.com" ]]; then
continue
fi
short="${sha:0:7}"
shortlink="[$short](https://github.com/${{ github.repository }}/commit/$sha)"
msg="${msg//|/\\|}"
new_prefix=""
if [[ -n "${since[$sha]:-}" ]]; then
new_prefix="✨ "
fi
info="$(gh api repos/$GITHUB_REPOSITORY/commits/$sha --jq '{email: .commit.author.email, login: (.author.login // .committer.login)}')"
login="$(jq -r '.login // empty' <<<"$info")"
if [[ -n "$login" && "$login" != "null" ]]; then
avatar="https://wsrv.nl/?url=github.com/${login}.png&w=48&h=48&mask=circle&fit=cover&maxage=1w"
else
email_lookup="$(jq -r '.email // empty' <<<"$info" | tr '[:upper:]' '[:lower:]')"
if [[ -n "$email_lookup" ]]; then
hash="$(printf '%s' "$email_lookup" | md5sum | awk '{print $1}')"
base="www.gravatar.com/avatar/$hash?d=identicon"
else
base="www.gravatar.com/avatar/?d=mp"
fi
avatar="https://wsrv.nl/?url=${base}&w=48&h=48&mask=circle&fit=cover&maxage=1w"
fi
author_text="$author"
author_text="${author_text//|/\\|}"
if [[ -n "$login" && "$login" != "null" ]]; then
author_cell="<a href=\"https://github.com/$login\"><img src=\"$avatar\" width=\"24\" height=\"24\" /></a> @$login"
else
author_cell="<img src=\"$avatar\" width=\"24\" height=\"24\" /> $author_text"
fi
version="-"
for t in "${rel_tags[@]}"; do
tcommit="$(git rev-list -n 1 "$t")"
if git merge-base --is-ancestor "$sha" "$tcommit"; then
version="$t"
break
fi
done
version="${new_prefix}${version}"
printf '| %s | %s | %s | %s |\n' "$author_cell" "$version" "$msg" "$shortlink" >> "$notes"
count=$((count+1))
if [[ $count -ge $limit ]]; then
break
fi
done
printf '| | | [View diff](https://github.com/%s/compare/v%s...latest) | |\n' "$GITHUB_REPOSITORY" "${{ steps.ver.outputs.old }}" >> "$notes"
printf '| | | [More recent commits](https://github.com/%s/commits/master/) | |\n' "$GITHUB_REPOSITORY" >> "$notes"
git tag -fa "$tag" "$upper" -m "Latest rolling release"
git push -f origin "refs/tags/$tag"
if gh release view "$tag" >/dev/null 2>&1; then
gh release edit "$tag" --notes-file "$notes" --title "v${{ steps.ver.outputs.new }} ($date_utc)" --latest
else
gh release create "$tag" --title "v${{ steps.ver.outputs.new }} ($date_utc)" --notes-file "$notes" --target "$upper" --latest
fi
{
echo "## ${{ steps.ver.outputs.new }}"
echo
cat "$notes"
} >> "$GITHUB_STEP_SUMMARY"
- name: Update latest branch
if: steps.ver.outputs.new != '' && steps.push.outputs.pushed == 'true'
shell: bash
run: |
set -euo pipefail
git push -f origin HEAD:refs/heads/release