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
140 changes: 107 additions & 33 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,50 +10,124 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Fetch all history for last tag and notes
fetch-depth: 0 # full history
- name: Install GH CLI
run: |
sudo apt update && sudo apt install curl -y
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list
sudo apt update && sudo apt install gh -y
gh auth login --with-token <<< ${{ secrets.DEMO_GITHUB_TOKEN }}
- name: Generate Release and Publish

- name: Generate Release and Publish (latest tag by time)
env:
GH_TOKEN: ${{ secrets.DEMO_GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.DEMO_GITHUB_TOKEN }}
run: |
# Get last tag or default to v1.0.0
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v1.0.0")
echo "Last tag: $LAST_TAG"
# Extract PR title prefix from merge commit message
PR_TITLE=$(git log -1 --merges --pretty=%s | sed 's/Merge pull request #[0-9]\+ from .*//')
echo "PR title prefix: $PR_TITLE"
# Determine version bump based on PR title prefix
MAJOR=$(echo "$PR_TITLE" | grep -E "^feat!:" && echo 1 || echo 0)
MINOR=$(echo "$PR_TITLE" | grep -E "^feat:" && echo 1 || echo 0) # Prioritize feat: for minor
PATCH=$(echo "$PR_TITLE" | grep -E "^\(fix:\|refactor:\|chore:\|docs:\|test:\)" && echo 1 || echo 0)
# Parse current version
CURRENT_MAJOR=$(echo $LAST_TAG | sed 's/v\([0-9]\+\)\.\([0-9]\+\)\.\([0-9]\+\)/\1/')
CURRENT_MINOR=$(echo $LAST_TAG | sed 's/v\([0-9]\+\)\.\([0-9]\+\)\.\([0-9]\+\)/\2/')
CURRENT_PATCH=$(echo $LAST_TAG | sed 's/v\([0-9]\+\)\.\([0-9]\+\)\.\([0-9]\+\)/\3/')
# Calculate new version with safety check
if [ "$TAG" = "$LAST_TAG" ] || [ $MAJOR -eq 0 ] && [ $MINOR -eq 0 ] && [ $PATCH -eq 0 ]; then
NEW_PATCH=$((CURRENT_PATCH + 1))
set -euo pipefail

# 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"

# 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
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')"

if [ -z "$CURRENT_MAJOR" ] || [ -z "$CURRENT_MINOR" ] || [ -z "$CURRENT_PATCH" ]; 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"

# 3) Get merged PR title associated with this push commit (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" ;;
esac
echo "Bump type: $BUMP"

# 5) Compute new version
if [ "$BUMP" = "major" ]; then
NEW_MAJOR=$((CURRENT_MAJOR + 1))
NEW_MINOR=0
NEW_PATCH=0
elif [ "$BUMP" = "minor" ]; then
NEW_MINOR=$((CURRENT_MINOR + 1))
NEW_PATCH=0
else
NEW_MAJOR=$((CURRENT_MAJOR + MAJOR))
NEW_MINOR=$((CURRENT_MINOR + MINOR * (1 - MAJOR)))
NEW_PATCH=$((CURRENT_PATCH + PATCH * (1 - MAJOR) * (1 - MINOR)))
NEW_PATCH=$((CURRENT_PATCH + 1))
fi

TAG="v${NEW_MAJOR}.${NEW_MINOR}.${NEW_PATCH}"
# Ensure tag is new
if git rev-parse "$TAG" >/dev/null 2>&1; then
echo "Tag $TAG already exists, incrementing patch..."

# 6) Ensure tag is unique
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}"
fi
done
echo "New tag: $TAG"
# Extract notes from commits since last tag, including merge commit context
NOTES=$(git log $LAST_TAG..HEAD --first-parent --pretty=format:"- %s" | grep -E "^(feat|fix|chore|docs|test|refactor):" || echo "Initial release")
echo "Release notes: $NOTES"
# Create tag and publish release
git tag $TAG
git push origin $TAG
gh release create $TAG --title "Release ${NEW_MAJOR}.${NEW_MINOR}.${NEW_PATCH}" --notes "$NOTES"

# 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
git tag "$TAG"
git push origin "$TAG"
gh release create "$TAG" \
--title "Release ${NEW_MAJOR}.${NEW_MINOR}.${NEW_PATCH}" \
--notes "$NOTES"