1616 contents : write
1717 pull-requests : write
1818 outputs :
19- release_created : ${{ steps.release.outputs.release_created }}
19+ pr : ${{ steps.release.outputs.pr }}
2020 steps :
2121 - name : Generate token
2222 id : generate-token
@@ -25,36 +25,208 @@ jobs:
2525 app-id : ${{ vars.SDK_BOT_APP_ID }}
2626 private-key : ${{ secrets.SDK_BOT_PRIVATE_KEY }}
2727
28+ # skip-github-release means release-please opens/updates release
29+ # PRs and updates CHANGELOG.md as usual, but does NOT tag or create
30+ # the GitHub Release on merge. Those are owned by publish-release
31+ # below so we can set the Release body from the rich CHANGELOG.md
32+ # section instead of release-please's terse default rendering.
2833 - name : Run release-please
2934 id : release
3035 uses : googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0
3136 with :
3237 token : ${{ steps.generate-token.outputs.token }}
38+ skip-github-release : true
3339
3440 - name : Checkout release PR branch
35- if : steps.release.outputs.prs_created == 'true'
41+ if : steps.release.outputs.pr
3642 uses : actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
3743 with :
38- ref : ${{ fromJSON(steps.release.outputs.prs)[0] .headBranchName }}
44+ ref : ${{ fromJSON(steps.release.outputs.pr) .headBranchName }}
3945 token : ${{ steps.generate-token.outputs.token }}
4046
4147 - name : Install uv
42- if : steps.release.outputs.prs_created == 'true'
48+ if : steps.release.outputs.pr
4349 uses : astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
4450
4551 - name : Update lockfile
46- if : steps.release.outputs.prs_created == 'true'
52+ if : steps.release.outputs.pr
4753 run : |
4854 uv lock
49- git config user.name "workos-bot [bot]"
50- git config user.email "workos-bot [bot]@users.noreply.github.com"
55+ git config user.name "workos-sdk-automation [bot]"
56+ git config user.email "255426317+ workos-sdk-automation [bot]@users.noreply.github.com"
5157 git add uv.lock
5258 git diff --staged --quiet || git commit -m "chore: update uv.lock"
5359 git push
5460
61+ # Inline pending changelog fragments under the version heading
62+ # release-please just wrote in CHANGELOG.md. For PRs that have a
63+ # fragment (the autogen flow always writes one), drop the line
64+ # release-please rendered and use the fragment instead. For PRs
65+ # without a fragment (typical for human-authored PRs), keep what
66+ # release-please wrote. Fragments are deleted in the same commit.
67+ # Idempotent: if no fragments exist, skip silently.
68+ - name : Inline rich changelog fragments
69+ if : steps.release.outputs.pr
70+ env :
71+ PR_JSON : ${{ steps.release.outputs.pr }}
72+ run : |
73+ set -euo pipefail
74+ shopt -s nullglob
75+ fragments=(.changelog-pending/*.md)
76+ if [ ${#fragments[@]} -eq 0 ]; then
77+ echo "No .changelog-pending fragments; leaving release-please CHANGELOG.md as-is."
78+ exit 0
79+ fi
80+
81+ VERSION=$(echo "$PR_JSON" | jq -r '.title' | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
82+ export VERSION
83+
84+ python3 - <<'PY'
85+ import os, re, pathlib, glob
86+
87+ version = os.environ["VERSION"]
88+
89+ # Load fragments + extract the PR number each one covers from
90+ # its top-line "* [#NN](url) ...".
91+ fragments = []
92+ covered = set()
93+ for path in sorted(glob.glob(".changelog-pending/*.md")):
94+ body = pathlib.Path(path).read_text().rstrip()
95+ m = re.search(r'\[#(\d+)\]', body)
96+ if m:
97+ covered.add(m.group(1))
98+ fragments.append(body)
99+
100+ changelog = pathlib.Path("CHANGELOG.md")
101+ text = changelog.read_text()
102+ section_re = re.compile(
103+ r'(^## \[' + re.escape(version) + r'\][^\n]*\n)(.*?)(?=^## |\Z)',
104+ re.MULTILINE | re.DOTALL,
105+ )
106+ match = section_re.search(text)
107+ if not match:
108+ raise SystemExit(f"Could not find '## [{version}]' heading in CHANGELOG.md")
109+ heading, body = match.group(1), match.group(2)
110+
111+ # Drop any release-please line that references a PR we have a
112+ # fragment for.
113+ kept = []
114+ for line in body.split("\n"):
115+ if any(pr in covered for pr in re.findall(r'\[#(\d+)\]', line)):
116+ continue
117+ kept.append(line)
118+ filtered = "\n".join(kept)
119+
120+ # Collapse "### Heading\n(blank lines)\n" with nothing under
121+ # it. Run repeatedly until stable in case of stacked empties.
122+ empty_section = re.compile(
123+ r'^### [^\n]*\n(?:\s*\n)*(?=^### |\Z)',
124+ re.MULTILINE,
125+ )
126+ while True:
127+ new = empty_section.sub('', filtered)
128+ if new == filtered:
129+ break
130+ filtered = new
131+ filtered = filtered.strip()
132+
133+ parts = []
134+ if filtered:
135+ parts.append(filtered)
136+ parts.extend(fragments)
137+ new_body = "\n\n".join(parts)
138+
139+ new_text = text[:match.start()] + heading + "\n" + new_body + "\n\n" + text[match.end():]
140+ changelog.write_text(new_text)
141+ PY
142+
143+ git config user.name "workos-sdk-automation[bot]"
144+ git config user.email "255426317+workos-sdk-automation[bot]@users.noreply.github.com"
145+ git rm .changelog-pending/*.md
146+ git add CHANGELOG.md
147+ git commit -m "chore: inline release notes from .changelog-pending"
148+ git push
149+
150+ # Detect when a release-please release PR has merged, then tag and
151+ # create the GitHub Release whose body is extracted from CHANGELOG.md
152+ # (now rich, after the inline step above). Runs on every push to main;
153+ # the detect step gates everything else.
154+ publish-release :
155+ runs-on : ubuntu-latest
156+ permissions :
157+ contents : write
158+ outputs :
159+ is-release : ${{ steps.detect.outputs.is-release }}
160+ version : ${{ steps.detect.outputs.version }}
161+ steps :
162+ - name : Generate token
163+ id : generate-token
164+ uses : actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # 3.1.1
165+ with :
166+ app-id : ${{ vars.SDK_BOT_APP_ID }}
167+ private-key : ${{ secrets.SDK_BOT_PRIVATE_KEY }}
168+
169+ - uses : actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
170+ with :
171+ fetch-depth : 0
172+ token : ${{ steps.generate-token.outputs.token }}
173+
174+ - name : Detect release PR merge
175+ id : detect
176+ run : |
177+ set -euo pipefail
178+ SUBJECT=$(git log -1 --format=%s)
179+ # release-please's default release PR title:
180+ # chore(main): release X.Y.Z
181+ if [[ "$SUBJECT" =~ ^chore\(.*\):[[:space:]]release[[:space:]]([0-9]+\.[0-9]+\.[0-9]+) ]]; then
182+ VERSION="${BASH_REMATCH[1]}"
183+ echo "is-release=true" >> "$GITHUB_OUTPUT"
184+ echo "version=$VERSION" >> "$GITHUB_OUTPUT"
185+ echo "Detected release PR merge for v$VERSION"
186+ else
187+ echo "Not a release PR merge: $SUBJECT"
188+ echo "is-release=false" >> "$GITHUB_OUTPUT"
189+ fi
190+
191+ - name : Extract release notes from CHANGELOG.md
192+ if : steps.detect.outputs.is-release == 'true'
193+ env :
194+ VERSION : ${{ steps.detect.outputs.version }}
195+ run : |
196+ set -euo pipefail
197+ awk -v v="$VERSION" '
198+ $0 ~ ("^## \\[" v "\\]") { found=1; next }
199+ found && /^## \[/ { exit }
200+ found
201+ ' CHANGELOG.md > /tmp/release-notes.md
202+ if [ ! -s /tmp/release-notes.md ]; then
203+ echo "::error::CHANGELOG.md has no body for v$VERSION"
204+ exit 1
205+ fi
206+
207+ - name : Tag and create GitHub Release
208+ if : steps.detect.outputs.is-release == 'true'
209+ env :
210+ GH_TOKEN : ${{ steps.generate-token.outputs.token }}
211+ VERSION : ${{ steps.detect.outputs.version }}
212+ run : |
213+ set -euo pipefail
214+ TAG="v$VERSION"
215+ if gh release view "$TAG" >/dev/null 2>&1; then
216+ echo "Release $TAG already exists; skipping."
217+ exit 0
218+ fi
219+ git config user.name "workos-sdk-automation[bot]"
220+ git config user.email "255426317+workos-sdk-automation[bot]@users.noreply.github.com"
221+ git tag -a "$TAG" -m "Release $TAG"
222+ git push origin "$TAG"
223+ gh release create "$TAG" \
224+ --title "$TAG" \
225+ --notes-file /tmp/release-notes.md
226+
55227 publish :
56- needs : release-please
57- if : needs.release-please .outputs.release_created == 'true'
228+ needs : publish-release
229+ if : needs.publish-release .outputs.is-release == 'true'
58230 runs-on : ubuntu-latest
59231 permissions :
60232 id-token : write
0 commit comments