Skip to content

Commit c7ac89e

Browse files
committed
fix(ci): support squash-merge bodies in auto-versioning and changelog
- Extend minor_pattern to match "* feat:" bullet lines from squash merges - Replace GitHub's auto-generated release notes with a custom changelog step that parses feat/fix lines including squash-merge bullet bodies - Update weekly promotion PR instructions to enforce merge commit strategy - Move PR_NUMBER to env var to avoid script-injection risk - Document merge commit requirement in CLAUDE.md
1 parent e596e77 commit c7ac89e

3 files changed

Lines changed: 72 additions & 7 deletions

File tree

.github/workflows/auto-versioning.yml

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ jobs:
4141
# Use a pattern that will never match to prevent automated major bumps
4242
major_pattern: "/__MANUAL_MAJOR_BUMP_ONLY__/"
4343
# Regex pattern for minor version bump (new features)
44-
# Matches: "feat:" prefix in commit messages (Conventional Commits)
45-
minor_pattern: "/^feat(\\(.+\\))?:/"
44+
# Matches "feat:" at line start, including "* feat:" bullet lines in squash-merge commit bodies
45+
minor_pattern: "/^(\\* )?feat(\\(.+\\))?:/"
4646
# Patch bumps: All other commits (fix:, chore:, etc.) are treated as patches by default
4747
# Pattern to determine formatting
4848
version_format: "${major}.${minor}.${patch}"
@@ -87,13 +87,69 @@ jobs:
8787
env:
8888
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
8989

90+
- name: Generate Release Changelog
91+
id: changelog
92+
if: ${{ steps.semver.outputs.changed == 'true' && steps.check_release.outputs.exists == 'false' }}
93+
run: |
94+
PREV_TAG=$(git describe --tags --abbrev=0 HEAD~1 2>/dev/null || echo "")
95+
NEW_TAG="${{ steps.determine_tag.outputs.tag }}"
96+
REPO="${{ github.repository }}"
97+
98+
rm -f /tmp/feat.txt /tmp/fix.txt
99+
touch /tmp/feat.txt /tmp/fix.txt
100+
101+
# Extract feat/fix lines from commit messages, including squash-merge bullet bodies ("* feat: ...")
102+
process_lines() {
103+
while IFS= read -r line; do
104+
clean="${line#\* }"
105+
if echo "$clean" | grep -qE '^feat(\([^)]+\))?!?:'; then
106+
echo "$clean" | sed -E 's/^feat(\([^)]+\))?!?: //' >> /tmp/feat.txt
107+
elif echo "$clean" | grep -qE '^(fix|perf)(\([^)]+\))?!?:'; then
108+
echo "$clean" | sed -E 's/^(fix|perf)(\([^)]+\))?!?: //' >> /tmp/fix.txt
109+
fi
110+
done
111+
}
112+
113+
if [ -n "$PREV_TAG" ]; then
114+
process_lines < <(git log --format="%B" "${PREV_TAG}..HEAD")
115+
else
116+
process_lines < <(git log --format="%B" -50 HEAD)
117+
fi
118+
119+
{
120+
echo "## What's Changed"
121+
echo ""
122+
if [ -s /tmp/feat.txt ]; then
123+
echo "### 🚀 Features"
124+
sed 's/^/- /' /tmp/feat.txt
125+
echo ""
126+
fi
127+
if [ -s /tmp/fix.txt ]; then
128+
echo "### 🐛 Bug Fixes & Improvements"
129+
sed 's/^/- /' /tmp/fix.txt
130+
echo ""
131+
fi
132+
if [ ! -s /tmp/feat.txt ] && [ ! -s /tmp/fix.txt ]; then
133+
echo "_Dependency updates and maintenance._"
134+
echo ""
135+
fi
136+
echo "---"
137+
echo ""
138+
if [ -n "$PREV_TAG" ]; then
139+
echo "**Full Changelog**: https://github.com/${REPO}/compare/${PREV_TAG}...${NEW_TAG}"
140+
fi
141+
} > /tmp/release_body.md
142+
143+
echo "Generated changelog:"
144+
cat /tmp/release_body.md
145+
90146
- name: Create GitHub Release (creates tag via API)
91147
if: ${{ steps.semver.outputs.changed == 'true' && steps.check_release.outputs.exists == 'false' }}
92148
uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3
93149
with:
94150
tag_name: ${{ steps.determine_tag.outputs.tag }}
95151
name: Release ${{ steps.determine_tag.outputs.tag }}
96-
generate_release_notes: true
152+
body_path: /tmp/release_body.md
97153
make_latest: true
98154
draft: false
99155
prerelease: false

.github/workflows/weekly-nightly-promotion.yml

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -333,13 +333,19 @@ jobs:
333333
- [ ] Changelog is up-to-date (auto-generated via workflow)
334334
- [ ] Version bump is appropriate (if applicable)
335335
336-
## Merge Instructions
336+
## ⚠️ Merge Instructions — CRITICAL
337337
338-
This PR promotes changes from \`nightly\` to \`main\`. Once all checks pass:
338+
> **You MUST use "Create a merge commit" — NOT squash or rebase.**
339+
>
340+
> Squash merging collapses all \`feat:\`/\`fix:\` commits into a single bullet-list body.
341+
> The auto-versioning workflow cannot parse these bullets, so minor version bumps are
342+
> silently skipped and release notes only show the weekly PR title instead of real changes.
343+
344+
Once all checks pass:
339345
340346
1. **Review** the commit summary above
341347
2. **Approve** if changes look correct
342-
3. **Merge** using "Merge commit" to preserve history
348+
3. **Merge** → click the dropdown arrow → select **"Create a merge commit"**
343349
344350
---
345351
@@ -430,6 +436,8 @@ jobs:
430436
steps:
431437
- name: Dispatch missing required workflows on nightly head
432438
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
439+
env:
440+
PR_NUMBER: ${{ needs.create-promotion-pr.outputs.pr_number }}
433441
with:
434442
script: |
435443
const owner = context.repo.owner;
@@ -443,7 +451,7 @@ jobs:
443451
const nightlyHeadSha = nightlyBranch.commit.sha;
444452
core.info(`Current nightly HEAD for dispatch fallback: ${nightlyHeadSha}`);
445453
446-
const prNumber = '${{ needs.create-promotion-pr.outputs.pr_number }}';
454+
const prNumber = process.env.PR_NUMBER;
447455
const requiredWorkflows = [
448456
{ id: 'e2e-tests-split.yml' },
449457
{ id: 'codeql.yml' },

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ Before proposing ANY code change or fix, build a mental map of the feature:
100100

101101
- **Triggers**: Use `feat:`, `fix:`, or `perf:` to trigger Docker builds. `chore:` skips builds.
102102
- **Beta**: `feature/beta-release` always builds.
103+
- **Weekly Promotion PRs** (`nightly → main`): ALWAYS merge using **"Create a merge commit"** — NEVER squash or rebase. Squash merging collapses all commits into bullet lines that the `auto-versioning` workflow cannot parse, silently preventing minor version bumps and producing empty release notes.
103104
- **History-Rewrite PRs**: If a PR touches files in `scripts/history-rewrite/` or `docs/plans/history_rewrite.md`, the PR description MUST include the history-rewrite checklist from `.github/PULL_REQUEST_TEMPLATE/history-rewrite.md`.
104105

105106
## PR Sizing & Decomposition

0 commit comments

Comments
 (0)