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
56 changes: 0 additions & 56 deletions .github/workflows/create-tag-and-exit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ on:

permissions:
contents: write
pull-requests: write

jobs:
tag:
Expand Down Expand Up @@ -46,58 +45,3 @@ jobs:
--generate-notes \
--title "${{ steps.version.outputs.tag }}"
fi

- name: Update CHANGELOG.md from releases
env:
GH_TOKEN: ${{ github.token }}
run: |
# Generate changelog header (no indentation to avoid leading spaces)
printf '%s\n' \
"# Changelog" \
"" \
"All notable changes to this project will be documented in this file." \
"The format is based on [Keep a Changelog](https://keepachangelog.com/)." \
"" \
"For releases prior to automated changelog generation, please see the" \
"[GitHub Releases](https://github.com/${{ github.repository }}/releases) page." \
"" > CHANGELOG.md

# Append all releases using JSON for reliable parsing (paginate to include all)
gh api --paginate repos/${{ github.repository }}/releases?per_page=100 \
--jq 'map({tagName: .tag_name, publishedAt: .published_at})' | jq -r '.[] | "\(.tagName) \(.publishedAt)"' | while read -r tag published_at; do
formatted_date=$(date -d "$published_at" +%Y-%m-%d 2>/dev/null || echo "$published_at")
echo "## [$tag] - $formatted_date"
echo ""
# Transform top-level headings (## ) to subheadings (### ) for changelog nesting.
# Uses negative lookahead pattern to avoid double-transforming already-nested headings.
gh release view "$tag" --json body -q '.body' | sed 's/^##\([^#]\)/###\1/'
echo ""
done >> CHANGELOG.md

- name: Create PR for CHANGELOG.md update
env:
GH_TOKEN: ${{ github.token }}
run: |
git config user.name "github-actions"
git config user.email "github-actions@github.com"
git add CHANGELOG.md
if git diff --staged --quiet; then
echo "No changelog changes to commit"
exit 0
fi

BRANCH_NAME="chore/update-changelog-${{ steps.version.outputs.tag }}"
git checkout -B "$BRANCH_NAME"
git commit -m "chore: update changelog for ${{ steps.version.outputs.tag }}"
git push --force-with-lease origin "$BRANCH_NAME"

# Create PR only if one doesn't already exist for this branch
if gh pr view "$BRANCH_NAME" --json state -q '.state' 2>/dev/null | grep -q "OPEN"; then
echo "PR already exists for $BRANCH_NAME; skipping creation."
else
gh pr create \
--title "chore: update changelog for ${{ steps.version.outputs.tag }}" \
--body "Automated changelog update for release ${{ steps.version.outputs.tag }}" \
--base main \
--head "$BRANCH_NAME"
fi
74 changes: 74 additions & 0 deletions .github/workflows/update-changelog.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
name: Update Changelog

on:
release:
types: [published]

permissions:
contents: write
pull-requests: write

jobs:
update-changelog:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Update CHANGELOG.md from releases
env:
GH_TOKEN: ${{ github.token }}
run: |
# Generate changelog header (no indentation to avoid leading spaces)
printf '%s\n' \
"# Changelog" \
"" \
"All notable changes to this project will be documented in this file." \
"The format is based on [Keep a Changelog](https://keepachangelog.com/)." \
"" \
"For releases prior to automated changelog generation, please see the" \
"[GitHub Releases](https://github.com/${{ github.repository }}/releases) page." \
"" > CHANGELOG.md

# Append all releases using JSON for reliable parsing (paginate to include all)
gh api --paginate repos/${{ github.repository }}/releases?per_page=100 \
| jq -s -r 'add | map({tagName: .tag_name, publishedAt: .published_at}) | .[] | "\(.tagName) \(.publishedAt)"' \
| while read -r tag published_at; do
Comment on lines +35 to +38
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

GitHub REST API List releases GET /repos/{owner}/{repo}/releases draft prerelease fields

💡 Result:

GET /repos/{owner}/{repo}/releases returns an array of release objects, and each release object includes these boolean fields:

  • draft: true if the release is a draft (unpublished), else false. [1]
  • prerelease: true if the release is marked as a prerelease, else false. [1]

Visibility note: only users with push access will have draft releases included in the list response; others only see published releases. [2]

Sources:
[1] GitHub REST API “List releases” example response schema (shows draft and prerelease). (docs.github.com)
[2] GitHub REST API “List releases” notes on draft visibility. (docs.github.com)

Citations:


Exclude draft releases from the changelog.

The releases API includes draft releases (for users with push access), which would surface unpublished notes in CHANGELOG.md. Filter them out in the jq pipeline using select(.draft == false) to avoid leaking draft content. If prereleases should also be excluded, add a prerelease == false predicate as well.

🔧 Proposed fix
-          gh api --paginate repos/${{ github.repository }}/releases?per_page=100 \
-            | jq -s -r 'add | map({tagName: .tag_name, publishedAt: .published_at}) | .[] | "\(.tagName) \(.publishedAt)"' \
+          gh api --paginate repos/${{ github.repository }}/releases?per_page=100 \
+            | jq -s -r 'add
+              | map(select(.draft == false) | {tagName: .tag_name, publishedAt: .published_at})
+              | .[] | "\(.tagName) \(.publishedAt)"' \
🤖 Prompt for AI Agents
In @.github/workflows/update-changelog.yml around lines 35 - 38, The jq pipeline
that builds the changelog currently includes draft releases; update the jq
expression used after the gh api call (the pipeline using jq -s -r 'add |
map({tagName: .tag_name, publishedAt: .published_at}) | .[] | "\(.tagName)
\(.publishedAt)"') to filter out draft releases by inserting a select(.draft ==
false) (and also select(.prerelease == false) if prereleases must be excluded)
before mapping, so only non-draft (and optionally non-prerelease) releases are
emitted to the while read loop.

formatted_date=$(date -d "$published_at" +%Y-%m-%d 2>/dev/null || echo "$published_at")
echo "## [$tag] - $formatted_date"
echo ""
# Transform top-level headings (## ) to subheadings (### ) for changelog nesting.
# Uses negative lookahead pattern to avoid double-transforming already-nested headings.
gh release view "$tag" --json body -q '.body' | sed 's/^##\([^#]\)/###\1/'
echo ""
done >> CHANGELOG.md

- name: Create PR for CHANGELOG.md update
env:
GH_TOKEN: ${{ github.token }}
run: |
git config user.name "github-actions"
git config user.email "github-actions@github.com"
git add CHANGELOG.md
if git diff --staged --quiet; then
echo "No changelog changes to commit"
exit 0
fi

BRANCH_NAME="chore/update-changelog-${{ github.event.release.tag_name }}"
git checkout -B "$BRANCH_NAME"
git commit -m "chore: update changelog for ${{ github.event.release.tag_name }}"
git push --force-with-lease origin "$BRANCH_NAME"

# Create PR only if one doesn't already exist for this branch
if gh pr view "$BRANCH_NAME" --json state -q '.state' 2>/dev/null | grep -q "OPEN"; then
echo "PR already exists for $BRANCH_NAME; skipping creation."
else
gh pr create \
--title "chore: update changelog for ${{ github.event.release.tag_name }}" \
--body "Automated changelog update for release ${{ github.event.release.tag_name }}" \
--base main \
--head "$BRANCH_NAME"
fi