Skip to content
Merged
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
116 changes: 45 additions & 71 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
sudo apt update && sudo apt install gh -y
gh auth login --with-token <<< ${{ secrets.DEMO_GITHUB_TOKEN }}

- name: Generate Release and Publish (latest tag by time)
- name: Generate Release and Publish (latest tag by time, auto notes)
env:
GH_TOKEN: ${{ secrets.DEMO_GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.DEMO_GITHUB_TOKEN }}
Expand All @@ -29,105 +29,79 @@ jobs:
# Ensure tags are present locally
git fetch --tags --force

# 1) Get latest tag by creation time (not ancestry). Fallback to v1.0.0
RAW_LAST_TAG="$(git for-each-ref --sort=-creatordate --format '%(refname:short)' refs/tags | head -n1 || true)"
if [ -z "${RAW_LAST_TAG:-}" ]; then
LAST_TAG="v1.0.0"
TAG_START_DATE="" # no previous tag; we'll treat notes as initial
else
LAST_TAG="$RAW_LAST_TAG"
fi

# Normalize to vX.Y.Z form for parsing
case "$LAST_TAG" in
v*) ;; # already has v
*) LAST_TAG="v$LAST_TAG" ;;
esac
echo "Last tag (by time): $LAST_TAG"
# Get newest and previous tags by creation time (not ancestry)
mapfile -t TAGS < <(git for-each-ref --sort=-creatordate --format '%(refname:short)' refs/tags)
RAW_LAST_TAG="${TAGS[0]:-}"
RAW_PREV_TAG="${TAGS[1]:-}"

# 1a) Determine the timestamp of the last tag (works for annotated/lightweight)
# Try taggerdate; if absent (lightweight), use the commit date of the tagged object.
TAG_START_DATE="$(git for-each-ref --format='%(taggerdate:iso8601)' "refs/tags/${LAST_TAG#v}" | head -n1 || true)"
if [ -z "${TAG_START_DATE:-}" ]; then
# For lightweight tags, resolve to the tagged object and use its committer date
TAG_COMMIT="$(git rev-list -n 1 "$LAST_TAG" 2>/dev/null || true)"
if [ -n "${TAG_COMMIT:-}" ]; then
TAG_START_DATE="$(git show -s --format=%cI "$TAG_COMMIT")"
fi
if [ -z "$RAW_LAST_TAG" ]; then
# No tags at all yet: start from v1.0.0
LAST_TAG_NORM="v1.0.0"
else
# Normalize newest tag to vX.Y.Z for version parsing
case "$RAW_LAST_TAG" in v*) LAST_TAG_NORM="$RAW_LAST_TAG" ;; *) LAST_TAG_NORM="v$RAW_LAST_TAG" ;; esac
fi
echo "Last tag date (iso8601): ${TAG_START_DATE:-<none>}"

# 2) Parse current version numbers from LAST_TAG (vX.Y.Z)
CURRENT_MAJOR="$(echo "$LAST_TAG" | sed -n 's/^v\([0-9]\+\)\.\([0-9]\+\)\.\([0-9]\+\)$/\1/p')"
CURRENT_MINOR="$(echo "$LAST_TAG" | sed -n 's/^v\([0-9]\+\)\.\([0-9]\+\)\.\([0-9]\+\)$/\2/p')"
CURRENT_PATCH="$(echo "$LAST_TAG" | sed -n 's/^v\([0-9]\+\)\.\([0-9]\+\)\.\([0-9]\+\)$/\3/p')"
echo "Newest tag by time: ${RAW_LAST_TAG:-<none>}"
echo "Previous tag by time: ${RAW_PREV_TAG:-<none>}"

if [ -z "$CURRENT_MAJOR" ] || [ -z "$CURRENT_MINOR" ] || [ -z "$CURRENT_PATCH" ]; then
echo "LAST_TAG not in vX.Y.Z format. Aborting."
# Parse current version numbers from LAST_TAG_NORM (vX.Y.Z)
CUR_MAJ="$(echo "$LAST_TAG_NORM" | sed -n 's/^v\([0-9]\+\)\.\([0-9]\+\)\.\([0-9]\+\)$/\1/p')"
CUR_MIN="$(echo "$LAST_TAG_NORM" | sed -n 's/^v\([0-9]\+\)\.\([0-9]\+\)\.\([0-9]\+\)$/\2/p')"
CUR_PAT="$(echo "$LAST_TAG_NORM" | sed -n 's/^v\([0-9]\+\)\.\([0-9]\+\)\.\([0-9]\+\)$/\3/p')"
if [ -z "$CUR_MAJ" ] || [ -z "$CUR_MIN" ] || [ -z "$CUR_PAT" ]; then
echo "Last tag not in vX.Y.Z format. Aborting."
exit 1
fi

NEW_MAJOR="$CURRENT_MAJOR"
NEW_MINOR="$CURRENT_MINOR"
NEW_PATCH="$CURRENT_PATCH"
NEW_MAJ="$CUR_MAJ"; NEW_MIN="$CUR_MIN"; NEW_PAT="$CUR_PAT"

# 3) Get merged PR title associated with this push commit (if any)
PR_TITLE="$(gh api \
-H "Accept: application/vnd.github+json" \
# Determine bump from merged PR title for this push (if any)
PR_TITLE="$(gh api -H "Accept: application/vnd.github+json" \
"repos/${{ github.repository }}/commits/${{ github.sha }}/pulls" \
--jq '.[0].title' || true)"
echo "PR title: ${PR_TITLE:-<none>}"

# 4) Decide bump via Conventional Commits
BUMP="patch"
case "${PR_TITLE:-}" in
feat!:*) BUMP="major" ;;
feat:*) BUMP="minor" ;;
fix:*|refactor:*|chore:*|docs:*|test:*) BUMP="patch" ;;
*) BUMP="patch" ;;
*) BUMP="patch" ;;
esac
echo "Bump type: $BUMP"

# 5) Compute new version
if [ "$BUMP" = "major" ]; then
NEW_MAJOR=$((CURRENT_MAJOR + 1))
NEW_MINOR=0
NEW_PATCH=0
NEW_MAJ=$((CUR_MAJ + 1)); NEW_MIN=0; NEW_PAT=0
elif [ "$BUMP" = "minor" ]; then
NEW_MINOR=$((CURRENT_MINOR + 1))
NEW_PATCH=0
NEW_MIN=$((CUR_MIN + 1)); NEW_PAT=0
else
NEW_PATCH=$((CURRENT_PATCH + 1))
NEW_PAT=$((CUR_PAT + 1))
fi

TAG="v${NEW_MAJOR}.${NEW_MINOR}.${NEW_PATCH}"
TAG="v${NEW_MAJ}.${NEW_MIN}.${NEW_PAT}"

# 6) Ensure tag is unique
# Ensure tag uniqueness
while git show-ref --tags --verify --quiet "refs/tags/$TAG"; do
echo "Tag $TAG already exists, incrementing patch…"
NEW_PATCH=$((NEW_PATCH + 1))
TAG="v${NEW_MAJOR}.${NEW_MINOR}.${NEW_PATCH}"
echo "Tag $TAG exists, bumping patch…"
NEW_PAT=$((NEW_PAT + 1))
TAG="v${NEW_MAJ}.${NEW_MIN}.${NEW_PAT}"
done
echo "New tag: $TAG"

# 7) Release notes:
# If we have a prior tag date, collect commits since that timestamp (first-parent).
# Otherwise (no prior tag), mark as initial release.
if [ -n "${TAG_START_DATE:-}" ]; then
NOTES="$(git log --since="$TAG_START_DATE" --first-parent --pretty=format:"- %s" | \
grep -E '^(feat|fix|chore|docs|test|refactor):' || true)"
else
NOTES=""
fi
if [ -z "$NOTES" ]; then
NOTES="Initial release"
fi
echo "Release notes:"
echo "$NOTES"

# 8) Create tag & publish release
# Create and push the tag
git tag "$TAG"
git push origin "$TAG"
gh release create "$TAG" \
--title "Release ${NEW_MAJOR}.${NEW_MINOR}.${NEW_PATCH}" \
--notes "$NOTES"

# Create the release with GitHub's auto-generated notes.
# If we have a previous tag by time, use it as the notes start.
if [ -n "${RAW_PREV_TAG:-}" ]; then
gh release create "$TAG" \
--title "Release ${NEW_MAJ}.${NEW_MIN}.${NEW_PAT}" \
--generate-notes \
--notes-start-tag "$RAW_PREV_TAG"
else
gh release create "$TAG" \
--title "Release ${NEW_MAJ}.${NEW_MIN}.${NEW_PAT}" \
--generate-notes
fi