Skip to content

Commit 37f628c

Browse files
authored
ci(regen): seed changelog under [Unreleased], drop version bump (#121)
* ci(regen): seed changelog under [Unreleased], drop version bump * ci(regen): use line-exact changelog idempotency check
1 parent 8d67148 commit 37f628c

2 files changed

Lines changed: 45 additions & 55 deletions

File tree

.github/workflows/check-release.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ jobs:
2727

2828
# Build + install + import the wheel here rather than in the regen workflow.
2929
# This job runs on any PR touching pyproject.toml/CHANGELOG.md — which every
30-
# regen PR does (version bump + seeded changelog) — so a packaging or import
31-
# regression surfaces as red CI on the PR instead of silently aborting the
32-
# regen before it can open one. (Step name kept stable to preserve the
30+
# regen PR does (it seeds a [Unreleased] changelog entry) — so a packaging or
31+
# import regression surfaces as red CI on the PR instead of silently aborting
32+
# the regen before it can open one. (Step name kept stable to preserve the
3333
# required-check / branch-protection wiring.)
3434
- name: Build, install, and import the wheel
3535
run: |

.github/workflows/regenerate.yml

Lines changed: 42 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -67,75 +67,64 @@ jobs:
6767
esac
6868
done
6969
70-
# pyproject.toml is hand-maintained (see .openapi-generator-ignore), so the
71-
# generator no longer stamps the version. Bump the patch version directly,
72-
# rewriting the same `^version = "X"` line scripts/release.sh treats as the
73-
# source of truth.
74-
- name: Bump package patch version in pyproject.toml
75-
id: pkg
76-
run: |
77-
version=$(python3 - <<'PY'
78-
import re, pathlib
79-
path = pathlib.Path("pyproject.toml")
80-
text = path.read_text()
81-
m = re.search(r'(?m)^version = "(\d+)\.(\d+)\.(\d+)"', text)
82-
if not m:
83-
raise SystemExit("could not find a semver version in pyproject.toml")
84-
new = f"{int(m[1])}.{int(m[2])}.{int(m[3]) + 1}"
85-
text, n = re.subn(r'(?m)^version = "[^"]+"', f'version = "{new}"', text, count=1)
86-
if n != 1:
87-
raise SystemExit("failed to rewrite version in pyproject.toml")
88-
path.write_text(text)
89-
print(new)
90-
PY
91-
)
92-
echo "version=$version" >> "$GITHUB_OUTPUT"
93-
94-
# check-release.yml gates merges on a `## [x.y.z]` CHANGELOG section
95-
# matching the bumped version, so without a seeded entry every regen PR
96-
# fails that check. Insert a stub (the spec-change title under ### Changed)
97-
# just above the most recent released section; the PR author refines the
98-
# wording before merge. Idempotent: skips if a section for this version
99-
# already exists.
100-
- name: Seed changelog entry
70+
# The version bump is intentionally NOT done here. A regen is just a set of
71+
# changes; which release they ship in (and the bump kind) is decided later
72+
# by scripts/release.sh prepare. Seed the regen notes under [Unreleased] so
73+
# they accumulate there until a release rolls them into a version. This
74+
# avoids minting a dated version section per regen that may never publish.
75+
- name: Seed changelog entry under [Unreleased]
10176
env:
102-
VERSION: ${{ steps.pkg.outputs.version }}
10377
TITLE: ${{ inputs.title }}
10478
run: |
105-
export CHANGELOG_DATE=$(date -u +%Y-%m-%d)
10679
python3 - <<'PY'
10780
import os, pathlib, re
108-
version = os.environ["VERSION"]
109-
date = os.environ["CHANGELOG_DATE"]
11081
title = (os.environ.get("TITLE") or "").strip() \
11182
or "Regenerate the client from the updated Hotdata OpenAPI spec"
83+
bullet = f"- {title}"
11284
path = pathlib.Path("CHANGELOG.md")
11385
text = path.read_text()
114-
if re.search(rf"^## \[{re.escape(version)}\]", text, re.M):
115-
print(f"CHANGELOG already has a [{version}] section; leaving it untouched.")
116-
raise SystemExit(0)
117-
unreleased = re.search(r"^## \[Unreleased\]", text, re.M)
118-
if not unreleased:
86+
87+
heading = re.search(r"^## \[Unreleased\][^\n]*\n", text, re.M)
88+
if not heading:
11989
raise SystemExit("CHANGELOG.md has no '## [Unreleased]' section to anchor the new entry")
120-
# Insert before the first released section after [Unreleased] (falling
121-
# back to end of file) so any pending entries under [Unreleased] stay
122-
# attributed to it rather than being absorbed by the new version.
123-
nxt = re.search(r"^## \[", text[unreleased.end():], re.M)
124-
insert_at = unreleased.end() + nxt.start() if nxt else len(text)
125-
entry = f"## [{version}] - {date}\n\n### Changed\n\n- {title}\n\n"
126-
text = text[:insert_at] + entry + text[insert_at:]
127-
path.write_text(text)
128-
print(f"Inserted CHANGELOG [{version}] section.")
90+
91+
# Scope edits to the [Unreleased] body (up to the next '## [' or EOF).
92+
start = heading.end()
93+
nxt = re.search(r"^## \[", text[start:], re.M)
94+
end = start + nxt.start() if nxt else len(text)
95+
body = text[start:end]
96+
97+
if any(line.strip() == bullet for line in body.splitlines()):
98+
print("CHANGELOG [Unreleased] already lists this entry; leaving it untouched.")
99+
raise SystemExit(0)
100+
101+
changed = re.search(r"^### Changed[ \t]*\n", body, re.M)
102+
if changed:
103+
# Prepend the bullet to the existing ### Changed list.
104+
i = changed.end()
105+
while i < len(body) and body[i] == "\n":
106+
i += 1
107+
body = body[:i] + bullet + "\n" + body[i:]
108+
else:
109+
# No ### Changed yet: open one right under the heading.
110+
body = "\n### Changed\n\n" + bullet + "\n\n" + body.lstrip("\n")
111+
112+
path.write_text(text[:start] + body + text[end:])
113+
print("Added regen entry under CHANGELOG [Unreleased].")
129114
PY
130115
116+
# No packageVersion: pyproject.toml is hand-maintained and the generator
117+
# doesn't stamp it, while __version__ and the SDK version string both read
118+
# from installed package metadata — so the generator's packageVersion never
119+
# reaches committed output.
131120
- name: Generate client
132121
run: |
133122
npx @openapitools/openapi-generator-cli generate \
134123
-i openapi.yaml \
135124
-g python \
136125
-o . \
137126
-t .openapi-generator-templates \
138-
--additional-properties=packageName=hotdata,projectName=hotdata,packageVersion=${{ steps.pkg.outputs.version }},gitUserId=hotdata-dev,gitRepoId=sdk-python \
127+
--additional-properties=packageName=hotdata,projectName=hotdata,gitUserId=hotdata-dev,gitRepoId=sdk-python \
139128
--skip-validate-spec
140129
141130
# pyproject.toml/requirements*.txt are hand-maintained, so the generator no
@@ -300,8 +289,9 @@ jobs:
300289
# on. The PR is the artifact we want, and breakage surfaces on it as red CI:
301290
# integration-tests.yml installs the package (`pip install -e .`) and runs
302291
# pytest, and check-release.yml builds + installs + imports the wheel on
303-
# every PR that bumps pyproject.toml/CHANGELOG.md (which every regen PR
304-
# does). Auto-merge is gated on those checks, so a broken regen can't merge.
292+
# every PR touching pyproject.toml/CHANGELOG.md (a regen seeds a [Unreleased]
293+
# changelog entry, so it always touches CHANGELOG.md). Auto-merge is gated on
294+
# those checks, so a broken regen can't merge.
305295

306296
- name: Check integration test scenario parity
307297
env:

0 commit comments

Comments
 (0)