Skip to content

Commit 4b72805

Browse files
ci: add release-drafter + prepare-release for faster releases (#260)
1 parent 81e01d7 commit 4b72805

5 files changed

Lines changed: 487 additions & 7 deletions

File tree

.github/release-drafter.yml

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# release-drafter config — accumulates merged-PR titles into a draft GitHub
2+
# Release as PRs land on main, so the English half of docs/changelog/v<ver>.md
3+
# is prefilled by the time we cut the next release.
4+
#
5+
# How it fits with the existing release flow:
6+
# - PRs merge → release-drafter updates the draft release tagged `next`
7+
# - When ready to ship, run `prepare-release.yml` which reads the draft
8+
# body and writes it into `docs/changelog/v<ver>.md` as a stub
9+
# - You translate the bullets into Persian above the `---` separator,
10+
# merge the prep PR, push the `v<ver>` tag, and release.yml takes over
11+
#
12+
# The draft is tagged `next` (not `vX.Y.Z`) so it never collides with the
13+
# real release-tag namespace. softprops/action-gh-release in release.yml
14+
# will create a fresh release for the actual `vX.Y.Z` tag — the `next`
15+
# draft just gets reset by release-drafter on the following PR merge.
16+
17+
name-template: 'Next release (draft)'
18+
tag-template: 'next'
19+
20+
# Flat bullet template — one line per merged PR, matching the existing
21+
# docs/changelog/v<ver>.md style:
22+
#
23+
# • <verb-first headline> ([#NN](url)): <full explanation>. Thanks @user
24+
#
25+
# We bake the `: <expand>. Thanks @AUTHOR` suffix directly into the
26+
# template so the maintainer's job is just (a) strip the leading
27+
# `feat:`/`fix:` Conventional-Commit prefix that PR titles in this repo
28+
# carry (prepare-release.yml does this automatically with a sed pass),
29+
# (b) fix the verb tense if needed (`added` → `Add`), and (c) replace
30+
# `<expand>` with the explanatory clause.
31+
#
32+
# Why the placeholder is part of the template and not added later:
33+
# putting it here means the no-changes-template fallback (below) does
34+
# *not* get a `<expand>` suffix — only real PR-derived bullets do.
35+
change-template: '• $TITLE ([#$NUMBER]($URL)): <expand>. Thanks @$AUTHOR'
36+
change-title-escapes: '\<*_&'
37+
38+
# Fallback if no PRs have merged since the last draft reset. Rare in
39+
# practice; here as a safety net so the draft body is never empty.
40+
# Deliberately doesn't follow the `<expand>`-bullet shape so it's
41+
# obviously a placeholder line, not a real release entry.
42+
no-changes-template: '_(no PR-tracked changes since the last release)_'
43+
44+
# Skip PRs labelled `release-prep` from the changelog — those are the
45+
# automated version-bump PRs opened by prepare-release.yml; including
46+
# them would echo "release: prepare v1.6.6" into the next release notes.
47+
exclude-labels:
48+
- 'release-prep'
49+
- 'skip-changelog'
50+
51+
# Auto-apply labels based on Conventional Commit title prefixes. The repo
52+
# already enforces feat:/fix:/etc. on PR titles, so this is "free" — no
53+
# contributor action needed. Labels feed the exclude-labels above and
54+
# also unlock PR filtering on the GitHub issues page if we want it later.
55+
autolabeler:
56+
- label: 'release-prep'
57+
title:
58+
- '/^release:/i'
59+
- label: 'type: feature'
60+
title:
61+
- '/^feat(\(.+\))?:/i'
62+
- label: 'type: fix'
63+
title:
64+
- '/^fix(\(.+\))?:/i'
65+
- label: 'type: chore'
66+
title:
67+
- '/^chore(\(.+\))?:/i'
68+
- label: 'type: docs'
69+
title:
70+
- '/^docs?(\(.+\))?:/i'
71+
- label: 'type: refactor'
72+
title:
73+
- '/^refactor(\(.+\))?:/i'
74+
75+
# Body of the draft release: just the flat bullet list. No "What's
76+
# Changed" header, no contributors block — keep it copy-paste-ready
77+
# into docs/changelog/v<ver>.md.
78+
template: |
79+
$CHANGES

.github/scripts/telegram_release_notify.py

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,42 @@
4646
from pathlib import Path
4747

4848

49+
def _strip_leading_comments(body: str) -> str:
50+
"""Strip leading HTML comment blocks (single- or multi-line) from `body`.
51+
52+
The changelog template uses `<!-- ... -->` to document the format for
53+
editors; we don't want those echoed to Telegram or the GitHub Release
54+
page. The `(?:...)+` quantifier eats N consecutive comments separated
55+
only by whitespace, so a stub with both a format-docs comment and a
56+
TODO comment is cleaned in one pass. `re.S` makes `.` cross newlines
57+
for multi-line `<!--\\n...\\n-->` blocks.
58+
59+
The matching regex is also used inline by .github/workflows/release.yml
60+
to compose the GitHub Release body — keep them in sync if you change
61+
one. Run `python -m doctest telegram_release_notify.py -v` to check.
62+
63+
>>> _strip_leading_comments("<!-- header -->\\nbody")
64+
'body'
65+
>>> _strip_leading_comments("<!-- a -->\\n<!-- b -->\\nbody")
66+
'body'
67+
>>> _strip_leading_comments("<!--\\nmulti\\nline\\n-->\\nbody")
68+
'body'
69+
>>> _strip_leading_comments("<!-- a -->\\n\\n<!-- b -->\\n\\nbody")
70+
'body'
71+
>>> _strip_leading_comments("body without comments")
72+
'body without comments'
73+
>>> _strip_leading_comments("body\\n<!-- mid-file comment -->\\nmore")
74+
'body\\n<!-- mid-file comment -->\\nmore'
75+
"""
76+
return re.sub(r"^\s*(?:<!--.*?-->\s*)+", "", body, count=1, flags=re.S)
77+
78+
4979
def parse_changelog(path: str) -> tuple[str, str]:
5080
"""Return (persian_body, english_body). Blank strings if file missing."""
5181
p = Path(path)
5282
if not p.is_file():
5383
return "", ""
54-
body = p.read_text(encoding="utf-8")
55-
# Strip a leading HTML comment block if present — the changelog
56-
# template uses <!-- ... --> to document the format for editors;
57-
# we don't want that echoed to Telegram.
58-
body = re.sub(r"^\s*<!--.*?-->\s*", "", body, count=1, flags=re.S)
84+
body = _strip_leading_comments(p.read_text(encoding="utf-8"))
5985
fa, sep, en = body.partition("\n---\n")
6086
if not sep:
6187
# No separator — treat everything as Persian (content-language

0 commit comments

Comments
 (0)