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
102 changes: 93 additions & 9 deletions .github/workflows/gen-release-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ jobs:
PATCH_COUNT=$((PATCH_COUNT + 1))
PATCH_PRS="$PATCH_PRS\n- $PR_TITLE (#$PR_NUM)"
fi

# Collect data for rich PR body
PR_AUTHOR=$(gh api repos/$OWNER/$REPO/pulls/$PR_NUM --jq '.user.login' 2>/dev/null || echo "unknown")
LABELS_JSON=$(echo "$LABELS" | jq -R -s 'split("\n") | map(select(length > 0))')
echo "{\"number\":$PR_NUM,\"title\":$(echo "$PR_TITLE" | jq -Rs .),\"author\":\"$PR_AUTHOR\",\"labels\":$LABELS_JSON}" >> pr-data.jsonl
fi
done <<< "$COMMITS"

Expand All @@ -110,6 +115,7 @@ jobs:

echo "Final release type: $RELEASE_TYPE"
echo "release-type=$RELEASE_TYPE" >> $GITHUB_OUTPUT
echo "latest-tag=$LATEST_TAG" >> $GITHUB_OUTPUT
echo "labeled-pr-list=$LABELED_PR_LIST" >> $GITHUB_OUTPUT

- name: Compute new version
Expand Down Expand Up @@ -166,22 +172,100 @@ jobs:
echo "Re-generated documentation"
git status

- name: Generate PR body
if: steps.version-update.outputs.new-version != ''
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
OWNER: ${{ github.repository_owner }}
REPO: ${{ github.event.repository.name }}
run: |
VERSION="${{ steps.version-update.outputs.new-version }}"
SHA="${{ steps.doc-gen.outputs.plugin-sha }}"
LATEST_TAG="${{ steps.unreleased.outputs.latest-tag }}"

# Generate release notes preview using shared script
./scripts/generate-release-notes.sh \
"$LATEST_TAG" "$VERSION" "$SHA" \
--file /tmp/release-notes.txt

# Read PR data
ALL_PRS=$(jq -s '.' pr-data.jsonl)

# Categorize PRs for the body list (just #XYZ, no title)
MAJOR_PRS=$(echo "$ALL_PRS" | jq -r '.[] | select(.labels | index("major")) | .author |= gsub("^app/"; "") | "- #\(.number) — @\(.author)"')
MINOR_PRS=$(echo "$ALL_PRS" | jq -r '.[] | select(.labels | index("minor")) | .author |= gsub("^app/"; "") | "- #\(.number) — @\(.author)"')
PATCH_PRS=$(echo "$ALL_PRS" | jq -r '.[] | select(.labels | index("patch")) | .author |= gsub("^app/"; "") | "- #\(.number) — @\(.author)"')
UNLABELED_PRS=$(echo "$ALL_PRS" | jq -r '.[] | select((.labels | index("major") | not) and (.labels | index("minor") | not) and (.labels | index("patch") | not) and (.labels | index("release") | not)) | .author |= gsub("^app/"; "") | "- #\(.number) — @\(.author)"')

CONTRIBUTORS=$(echo "$ALL_PRS" | jq -r '[.[].author] | unique | map(gsub("^app/"; "")) | .[] | "@\(.)"' | paste -sd ', ')

# Build the PR body header
cat > pr-body.md <<- BODY
## Automated Release Prep

**Release Type:** ${{ steps.unreleased.outputs.release-type }}
**New Version:** ${VERSION}

---

## 📋 What's included in this release

### 📦 Merged PRs (since \`${LATEST_TAG}\`)
BODY

# Add categorized PRs (just #XYZ format)
if [ -n "$MAJOR_PRS" ]; then
echo -e "\n**🚨 Major Changes**\n$MAJOR_PRS" >> pr-body.md
fi
if [ -n "$MINOR_PRS" ]; then
echo -e "\n**✨ New Features**\n$MINOR_PRS" >> pr-body.md
fi
if [ -n "$PATCH_PRS" ]; then
echo -e "\n**🐛 Bug Fixes**\n$PATCH_PRS" >> pr-body.md
fi
if [ -n "$UNLABELED_PRS" ]; then
echo -e "\n**📝 Other / Infrastructure**\n$UNLABELED_PRS" >> pr-body.md
fi

echo -e "\n\n**Contributors:** ${CONTRIBUTORS}" >> pr-body.md

# Add collapsible release notes preview
cat >> pr-body.md <<- PREVIEW

---

## 🔮 Release Notes Preview

<details>
<summary>Click to see what the release notes will look like</summary>

\`\`\`\`
PREVIEW

cat /tmp/release-notes.txt >> pr-body.md

cat >> pr-body.md <<- PREVIEW
\`\`\`\`

</details>

---

> **ℹ️** This PR was created automatically. It updates documentation with the release version and sha.
> When merged, the Release workflow will create the GitHub release with the accumulated release notes.
PREVIEW

echo "Generated PR body:"
cat pr-body.md

- name: Create release PR
if: steps.version-update.outputs.new-version != ''
uses: peter-evans/create-pull-request@v5
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "chore: prepare release ${{ steps.version-update.outputs.new-version }}"
title: "Release Prep ${{ steps.version-update.outputs.new-version }}"
body: |
## Automated Release Prep

**Release Type:** ${{ steps.unreleased.outputs.release-type }}
**New Version:** ${{ steps.version-update.outputs.new-version }}

This PR was created automatically. It updates documentation with the release version and sha.

When merged, the Release workflow will create the GitHub release with the accumulated release notes.
body-path: pr-body.md
branch: release-prep
base: ${{ github.ref_name }}
labels: |
Expand Down
43 changes: 5 additions & 38 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,51 +70,18 @@ jobs:
id: notes
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
OWNER: ${{ github.repository_owner }}
REPO: ${{ github.event.repository.name }}
run: |
OWNER="${{ github.repository_owner }}"
REPO="${{ github.event.repository.name }}"
VERSION=${{ steps.extract-version.outputs.version }}

PREVIOUS_TAG=$(git tag --sort=-v:refname | head -1)

if [ -z "$PREVIOUS_TAG" ]; then
COMMITS=$(git log --oneline --grep="Merge pull request" --grep="#[0-9]" -E)
else
COMMITS=$(git log ${PREVIOUS_TAG}..HEAD --oneline --grep="Merge pull request" --grep="#[0-9]" -E)
fi

NOTES_FILE=$(mktemp)
echo "notes-file=$NOTES_FILE" >> $GITHUB_OUTPUT

cat > "$NOTES_FILE" <<- ENDOFNOTE
## Release ${VERSION}

Release sha256 is \`${SHA256_HASH}\`

## Configuration example
\`\`\`yaml
version: '2'
plugins:
- name: csharp
wasm:
url: https://github.com/${{ github.repository }}/releases/download/${VERSION}/sqlc-gen-csharp.wasm
sha256: ${SHA256_HASH}
\`\`\`

## Changelog
ENDOFNOTE

while IFS= read -r commit; do
PR_NUM=$(echo "$commit" | grep -oE '#[0-9]+' | tr -d '#' || echo "")
if [ -n "$PR_NUM" ]; then
PR_TITLE=$(gh api repos/$OWNER/$REPO/pulls/$PR_NUM --jq '.title' 2>/dev/null || echo "PR #$PR_NUM")
echo "- $PR_TITLE (#$PR_NUM)" >> "$NOTES_FILE"
fi
done <<< "$COMMITS"

echo "" >> "$NOTES_FILE"
echo "## Contributors" >> "$NOTES_FILE"
echo "* @SockworkOrange" >> "$NOTES_FILE"
./scripts/generate-release-notes.sh \
"$PREVIOUS_TAG" "$VERSION" "$SHA256_HASH" \
--file "$NOTES_FILE"

- name: Create release and upload asset
env:
Expand Down
97 changes: 97 additions & 0 deletions scripts/generate-release-notes.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#!/usr/bin/env bash
# generate-release-notes.sh
#
# Shared release notes generator for sqlc-gen-csharp.
# Only includes PRs with major/minor/patch labels (excludes skip-release).
#
# Usage:
# OWNER=... REPO=... GH_TOKEN=... ./scripts/generate-release-notes.sh \
# <prev-tag> <new-version> <sha256> [--file <path>]

set -euo pipefail

: "${OWNER:?}" "${REPO:?}" "${GH_TOKEN:?}"

PREVIOUS_TAG="${1:?Usage: generate-release-notes.sh <prev-tag> <new-version> <sha256> [--file <path>]}"
NEW_VERSION="${2:?}"
SHA256_HASH="${3:?}"
OUTPUT_FILE=""
shift 3

while [ "$#" -gt 0 ]; do
case "$1" in
--file) OUTPUT_FILE="$2"; shift 2 ;;
*) echo "Unknown option: $1" >&2; exit 1 ;;
esac
done

# Get commits between tags (match PR numbers like #123)
if [ -z "$PREVIOUS_TAG" ]; then
COMMITS=$(git log --oneline --grep="#[0-9]" -E 2>/dev/null || echo "")
else
COMMITS=$(git log "${PREVIOUS_TAG}..HEAD" --oneline --grep="#[0-9]" -E 2>/dev/null || echo "")
fi

CHANGELOG_ITEMS=()
CONTRIBUTORS=()

while IFS= read -r commit; do
[ -z "$commit" ] && continue
PR_NUM=$(echo "$commit" | grep -oE '#[0-9]+' | tr -d '#' || echo "")
[ -z "$PR_NUM" ] && continue

PR_JSON=$(gh api "repos/$OWNER/$REPO/pulls/$PR_NUM" \
--jq '{title: .title, author: .user.login, labels: [.labels[].name]}' 2>/dev/null || echo '{}')
PR_TITLE=$(echo "$PR_JSON" | jq -r '.title // empty')
PR_AUTHOR=$(echo "$PR_JSON" | jq -r '.author // empty')
[ -z "$PR_TITLE" ] && continue

HAS_LABEL=false
SKIP_RELEASE=false
while IFS= read -r label; do
[ -z "$label" ] && continue
case "$label" in
major|minor|patch) HAS_LABEL=true ;;
skip-release) SKIP_RELEASE=true ;;
esac
done <<< "$(echo "$PR_JSON" | jq -r '.labels[] // empty')"

if $HAS_LABEL && ! $SKIP_RELEASE; then
CHANGELOG_ITEMS+=("- $PR_TITLE (#$PR_NUM)")
CLEAN_AUTHOR="${PR_AUTHOR#app/}"
FOUND=false
for c in "${CONTRIBUTORS[@]}"; do
[ "$c" = "$CLEAN_AUTHOR" ] && FOUND=true && break
done
$FOUND || CONTRIBUTORS+=("$CLEAN_AUTHOR")
fi
done <<< "$COMMITS"

# Write output
if [ -n "$OUTPUT_FILE" ]; then
exec > "$OUTPUT_FILE"
fi

echo "## Release ${NEW_VERSION}"
echo ""
echo "Release sha256 is \`${SHA256_HASH}\`"
echo ""
echo "## Configuration example"
echo '```yaml'
echo "version: '2'"
echo "plugins:"
echo "- name: csharp"
echo " wasm:"
echo " url: https://github.com/${OWNER}/${REPO}/releases/download/${NEW_VERSION}/sqlc-gen-csharp.wasm"
echo " sha256: ${SHA256_HASH}"
echo '```'
echo ""
echo "## Changelog"
for item in "${CHANGELOG_ITEMS[@]}"; do
echo "$item"
done
echo ""
echo "## Contributors"
for author in "${CONTRIBUTORS[@]}"; do
echo "* @${author}"
done
Loading