fix(release): a dateless changelog header silently skipped the release
Demo images / Build & push demo images (push) Failing after 38s
Demo images / Build & push demo images (push) Failing after 38s
create-release aborted at [4/8] with "web/dist/neode-ui does not contain
v1.8.4-alpha — the frontend build no-opped or its output is stale". The
build had not no-opped: it was fresh, and simply had no 1.8.4 string to
embed.
sync-whats-new.py only matches '## vX.Y.Z (YYYY-MM-DD)'. The entry read
'## v1.8.4-alpha (draft — date set at cut)', so the version was invisible
to it: the gate's whats-new-sync stage reported "87 versions, all present"
while the release being cut had no What's New block. That modal is the
only place a version string appears in the frontend, so the bundle carried
none and the freshness check — correctly — refused it, while naming the
wrong cause. Step [5/8] only greps for '^## v1.8.4-alpha (' so it passed
the draft too.
Three changes: date the v1.8.4-alpha entry, insert the modal block it was
owed, and make the sync tool refuse any version header without a real date
instead of skipping it. Skipping is what let a wrong "all present" through.
Verified: the draft header now fails the check with an explicit message,
the dated one passes (88 versions, up from 87), and a rebuilt bundle
contains 1.8.4-alpha where it did not before.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
60c1db98bb
commit
3d7de3e902
@@ -28,6 +28,15 @@ MONTHS = ["", "January", "February", "March", "April", "May", "June", "July",
|
||||
|
||||
HEADER_RE = re.compile(r"^## (v\d+\.\d+\.\d+\S*) \((\d{4})-(\d{2})-(\d{2})\)")
|
||||
|
||||
# A header that names a version but carries no YYYY-MM-DD date. Such a line
|
||||
# does not match HEADER_RE, so the version is INVISIBLE here — the tool then
|
||||
# reports "all present" while the very release being cut has no modal block.
|
||||
# That is what happened to v1.8.4-alpha on 2026-08-20: the changelog said
|
||||
# "(draft — date set at cut)", this check passed, and create-release then
|
||||
# aborted at the frontend step because the built bundle contained no version
|
||||
# string at all (the What's New modal is the only place one appears).
|
||||
UNDATED_RE = re.compile(r"^## (v\d+\.\d+\.\d+\S*) \((?!\d{4}-\d{2}-\d{2}\))")
|
||||
|
||||
|
||||
def parse_changelog():
|
||||
"""Return [(version, 'Month D, YYYY', [bullet, ...]), ...] newest-first."""
|
||||
@@ -48,6 +57,16 @@ def parse_changelog():
|
||||
return entries
|
||||
|
||||
|
||||
def undated_versions():
|
||||
"""[(version, whole line), ...] for version headers with no real date."""
|
||||
found = []
|
||||
for line in CHANGELOG.read_text().splitlines():
|
||||
m = UNDATED_RE.match(line)
|
||||
if m:
|
||||
found.append((m.group(1), line.strip()))
|
||||
return found
|
||||
|
||||
|
||||
def existing_versions():
|
||||
text = MODAL.read_text()
|
||||
return set(re.findall(r"<!-- (v\d+\.\d+\.\d+\S*) -->", text))
|
||||
@@ -78,6 +97,21 @@ def render_block(entry):
|
||||
|
||||
def main():
|
||||
check = "--check" in sys.argv
|
||||
|
||||
# Refuse to reason about a changelog whose newest entry has no date:
|
||||
# silently skipping it is what makes this check pass while the release
|
||||
# is in fact missing its modal block.
|
||||
undated = undated_versions()
|
||||
if undated:
|
||||
for ver, line in undated:
|
||||
print(f"FAIL: CHANGELOG entry for {ver} carries no release date:",
|
||||
file=sys.stderr)
|
||||
print(f" {line}", file=sys.stderr)
|
||||
print("A dated '## vX.Y.Z (YYYY-MM-DD)' header is required before the "
|
||||
"modal can be synced — without it the version is invisible to "
|
||||
"this tool and never reaches the built frontend.", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
entries = parse_changelog()
|
||||
have = existing_versions()
|
||||
missing = [e for e in entries if e["ver"] not in have]
|
||||
|
||||
Reference in New Issue
Block a user