diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 31789fb..5dd5a43 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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 }} @@ -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:-}" - # 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:-}" + echo "Previous tag by time: ${RAW_PREV_TAG:-}" - 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:-}" - # 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