Skip to content

Commit 5023a21

Browse files
authored
docs: automated release notes (#1293)
* chore: create generate release notes script * chore: update scheduled release github workflow * chore: cherry pick release notes script * chore: update build rc workflow to run script * chore: remove chmod
1 parent b58ca49 commit 5023a21

4 files changed

Lines changed: 269 additions & 0 deletions

File tree

.github/workflows/build-rc.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,44 @@ jobs:
5050
pr_head_sha: ${{ inputs.pr_head_sha || github.event.pull_request.head.sha }}
5151

5252
steps:
53+
- name: Create GitHub App Token
54+
uses: actions/create-github-app-token@v1
55+
id: app-token
56+
with:
57+
app-id: ${{ vars.RELEASE_BOT_APP_ID }}
58+
private-key: ${{ secrets.RELEASE_BOT_PRIVATE_KEY }}
59+
5360
- name: Checkout repository
5461
uses: actions/checkout@v4
5562
with:
5663
ref: ${{ env.BUILD_SHA }}
64+
fetch-depth: 0
65+
66+
- name: Parse version
67+
id: version
68+
uses: ./.github/parse-version
69+
70+
- name: Generate cherry pick release notes
71+
if: ${{ github.event_name == 'workflow_call' }}
72+
env:
73+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
74+
RELEASE_BRANCH: rc/v${{ steps.version.outputs.releaseMinor }}.0
75+
run: |
76+
./scripts/generate-cherry-pick-release-notes.sh
77+
78+
# Format the release notes
79+
npx prettier --write release-notes/**/*.md
80+
81+
git config --global user.name 'awana-release-bot[bot]'
82+
git config --global user.email '${{ vars.RELEASE_BOT_USER_ID }}+awana-release-bot[bot]@users.noreply.github.com'
83+
84+
# Fetch and checkout the branch
85+
git fetch origin $RELEASE_BRANCH
86+
git checkout $RELEASE_BRANCH
87+
88+
git add release-notes/
89+
git commit -m "docs: add cherry pick commits to release notes"
90+
git push origin $RELEASE_BRANCH
5791
5892
- name: Setup Node.js
5993
uses: actions/setup-node@v4

.github/workflows/scheduled-release.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ jobs:
4545
uses: actions/checkout@v4
4646
with:
4747
ref: ${{ env.TRUNK_BRANCH }}
48+
fetch-depth: 0
4849
token: ${{ steps.app-token.outputs.token }}
4950

5051
- name: Setup Node.js
@@ -69,6 +70,18 @@ jobs:
6970
git branch ${{ steps.branches.outputs.release }}
7071
git checkout -b ${{ steps.branches.outputs.rc }}
7172
73+
- name: Generate release notes
74+
env:
75+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
76+
run: |
77+
./scripts/generate-release-notes.sh
78+
79+
# Format the release notes
80+
npx prettier --write release-notes/**/*.md
81+
82+
git add release-notes/
83+
git commit -m "docs: add release notes for v${{ steps.version.outputs.releaseShort }}"
84+
7285
- name: Set target release version in package.json
7386
run: |
7487
npm version ${{ steps.version.outputs.releaseVersion }} --no-git-tag-version
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
#!/bin/bash
2+
3+
MINOR_VERSION=$(jq -r .version package.json | cut -d. -f2)
4+
5+
RELEASE_NOTES_FILE="release-notes/closed-prs-v$MINOR_VERSION.md"
6+
CLOSED_ISSUES_FILE="release-notes/closed-issues-v$MINOR_VERSION.md"
7+
8+
mkdir -p release-notes
9+
10+
# Branch name we're looking for
11+
RC_BRANCH="rc/v$MINOR_VERSION.0"
12+
13+
# Get PR number from that branch
14+
PR_NUMBER=$(gh pr list --head "$RC_BRANCH" --json number --jq '.[0].number')
15+
16+
if [ -z "$PR_NUMBER" ]; then
17+
echo "❌ No PR found for branch: $RC_BRANCH"
18+
exit 0
19+
fi
20+
21+
echo "✅ Found PR: #$PR_NUMBER"
22+
23+
# Get PR details including comments
24+
PR_DATA=$(gh pr view "$PR_NUMBER" --comments --json createdAt,comments)
25+
26+
# Extract commit headlines (aka message headers)
27+
COMMIT_HEADERS=$(gh pr view "$PR_NUMBER" --json commits --jq '.commits[]
28+
| {
29+
messageHeadline: .messageHeadline,
30+
createdAt: .committedDate
31+
}
32+
' | jq -s '.')
33+
34+
echo "Extracted commit headers for PR #$COMMIT_HEADERS"
35+
36+
if [ -z "$COMMIT_HEADERS" ]; then
37+
echo "❌ No commits found in PR #$PR_NUMBER"
38+
exit 0
39+
fi
40+
41+
# Extract the PR creation date
42+
pr_created_at=$(echo "$PR_DATA" | jq -r '.createdAt')
43+
44+
# Try to find last comment with body "/build-rc"
45+
cherry_pick_date=$(echo "$PR_DATA" | jq -r --arg body "/build-rc" '
46+
.comments
47+
| map(select(.body == $body))
48+
| sort_by(.createdAt)
49+
| if length >= 2 then .[-2].createdAt else null end
50+
')
51+
52+
# Set CHERRY_PICK_START_DATE
53+
if [[ -n "$cherry_pick_date" && "$cherry_pick_date" != "null" ]]; then
54+
CHERRY_PICK_START_DATE="$cherry_pick_date"
55+
else
56+
CHERRY_PICK_START_DATE="$pr_created_at"
57+
echo "No '/build-rc' comment found. Using PR creation date: $CHERRY_PICK_START_DATE"
58+
fi
59+
60+
echo "Using cherry-pick start date: $CHERRY_PICK_START_DATE"
61+
62+
FILTERED_COMMITS=$(echo "$COMMIT_HEADERS" | jq --arg start "$CHERRY_PICK_START_DATE" '
63+
map(select(.createdAt >= $start))
64+
')
65+
66+
# Extract PR numbers from headlines like: fix: something (#1234)
67+
PR_REFERENCES=$(echo "$FILTERED_COMMITS" | grep -oE '\(#([0-9]+)\)' | grep -oE '[0-9]+' | sort -n | uniq)
68+
69+
echo "Found PR references: $PR_REFERENCES"
70+
71+
if [ -z "$PR_REFERENCES" ]; then
72+
echo "❌ No PR numbers found in commit message headlines"
73+
exit 0
74+
fi
75+
76+
CHERRY_PICKED_COMMITS="[]"
77+
78+
for pr_number in $PR_REFERENCES; do
79+
PR_JSON=$(gh pr view "$pr_number" --json title,body,mergedAt,number)
80+
CHERRY_PICKED_COMMITS=$(jq --argjson new "$PR_JSON" '. += [$new]' <<< "$CHERRY_PICKED_COMMITS")
81+
done
82+
83+
DATE_TIME=$(TZ="America/New_York" date "+%Y-%m-%d %H:%M:%S %Z")
84+
85+
86+
# Format release notes
87+
{
88+
echo ""
89+
echo "## Closed Prs cherry picked onto RC created on $DATE_TIME"
90+
echo "$CHERRY_PICKED_COMMITS" | jq -r '
91+
sort_by(.mergedAt)[] |
92+
(
93+
"PR #\(.number): \(.title)",
94+
(
95+
(.body // "" | split("\n")[] |
96+
select(test("(?i)(close[sd]?|fix(e[sd])?|resolve[sd]?):?\\s*#[0-9]+")) |
97+
capture(".*#(?<issue>[0-9]+).*") |
98+
"[closed #\(.issue)](https://github.com/digidem/comapeo-mobile/issues/\(.issue))")
99+
// empty
100+
),
101+
"" # Always print a blank line at the end of each PR
102+
)
103+
'
104+
} >> "$RELEASE_NOTES_FILE"
105+
{
106+
107+
echo ""
108+
echo "## Closed Issues Added to RC created on $DATE_TIME"
109+
# Extract unique issue numbers
110+
ISSUE_NUMBERS=$(echo "$CHERRY_PICKED_COMMITS" | jq -r '
111+
[.[] |
112+
(.body // "" | split("\n")[] |
113+
select(test("(?i)(close[sd]?|fix(e[sd])?|resolve[sd]?):?\\s*#[0-9]+")) |
114+
capture(".*#(?<issue>[0-9]+).*") |
115+
.issue)
116+
] | unique | .[]')
117+
118+
# Loop through each issue and fetch title via GitHub CLI
119+
while read -r ISSUE; do
120+
TITLE=$(gh issue view "$ISSUE" --json title -q .title 2>/dev/null)
121+
if [ -n "$TITLE" ]; then
122+
echo "[closed #$ISSUE](https://github.com/digidem/comapeo-mobile/issues/$ISSUE): $TITLE"
123+
echo ""
124+
fi
125+
done <<< "$ISSUE_NUMBERS"
126+
127+
} >> "$CLOSED_ISSUES_FILE"

scripts/generate-release-notes.sh

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#!/bin/bash
2+
3+
MINOR_VERSION=$(jq -r .version package.json | cut -d. -f2)
4+
5+
START_COMMIT_MSG="chore: start v$MINOR_VERSION development iteration"
6+
END_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ") # current UTC time
7+
8+
RELEASE_NOTES_FILE="release-notes/closed-prs-v$MINOR_VERSION.md"
9+
CLOSED_ISSUES_FILE="release-notes/closed-issues-v$MINOR_VERSION.md"
10+
11+
mkdir -p release-notes
12+
13+
# Get SHAs
14+
START_SHA=$(git log --oneline | grep -F "$START_COMMIT_MSG" | awk '{print $1}' | head -n 1)
15+
16+
17+
if [ -z "$START_SHA" ]; then
18+
echo "Could not find start commit."
19+
exit 0
20+
fi
21+
22+
# Get ISO8601 timestamps
23+
START_DATE=$(git log -1 --format=%aI "$START_SHA")
24+
25+
echo "Using release window:"
26+
echo " $START_DATE ($START_COMMIT_MSG)"
27+
echo " $END_DATE"
28+
echo ""
29+
30+
# Fetch all merged PRs (no search, no limit)
31+
echo "Fetching all merged PRs..."
32+
ALL_PRS=$(gh pr list \
33+
--state merged \
34+
--json number,title,body,url,labels,mergedAt \
35+
--limit 1000)
36+
37+
# Filter by merge date
38+
FILTERED_PRS=$(echo "$ALL_PRS" | jq --arg start "$START_DATE" --arg end "$END_DATE" '
39+
map(select(.mergedAt >= $start and .mergedAt <= $end))')
40+
41+
COUNT=$(echo "$FILTERED_PRS" | jq length)
42+
echo "Found $COUNT PRs in range."
43+
44+
# Format release notes
45+
{
46+
echo "# Closed Prs for v$MINOR_VERSION"
47+
echo ""
48+
echo " PRs merged between:"
49+
echo "> - $START_COMMIT_MSG ($START_DATE, SHA: $START_SHA)"
50+
echo "> - $END_DATE"
51+
echo ""
52+
53+
echo "$FILTERED_PRS" | jq -r '
54+
sort_by(.mergedAt)[] |
55+
(
56+
"PR #\(.number): \(.title)",
57+
(
58+
(.body // "" | split("\n")[] |
59+
select(test("(?i)(close[sd]?|fix(e[sd])?|resolve[sd]?):?\\s*#[0-9]+")) |
60+
capture(".*#(?<issue>[0-9]+).*") |
61+
"[closed #\(.issue)](https://github.com/digidem/comapeo-mobile/issues/\(.issue))")
62+
// empty
63+
),
64+
"" # Always print a blank line at the end of each PR
65+
)
66+
'
67+
68+
# Now create the summary list of unique closed issues
69+
70+
echo ""
71+
} >> "$RELEASE_NOTES_FILE"
72+
{
73+
echo "# Closed Issues for v$MINOR_VERSION"
74+
echo ""
75+
76+
# Extract unique issue numbers
77+
ISSUE_NUMBERS=$(echo "$FILTERED_PRS" | jq -r '
78+
[.[] |
79+
(.body // "" | split("\n")[] |
80+
select(test("(?i)(close[sd]?|fix(e[sd])?|resolve[sd]?):?\\s*#[0-9]+")) |
81+
capture(".*#(?<issue>[0-9]+).*") |
82+
.issue)
83+
] | unique | .[]')
84+
85+
# Loop through each issue and fetch title via GitHub CLI
86+
while read -r ISSUE; do
87+
TITLE=$(gh issue view "$ISSUE" --json title -q .title 2>/dev/null)
88+
if [ -n "$TITLE" ]; then
89+
echo "[closed #$ISSUE](https://github.com/digidem/comapeo-mobile/issues/$ISSUE): $TITLE"
90+
echo ""
91+
fi
92+
done <<< "$ISSUE_NUMBERS"
93+
94+
} >> "$CLOSED_ISSUES_FILE"
95+

0 commit comments

Comments
 (0)