From db6b0ce5b0157a9bed0bf3c8c3c30b0dcb85d48c Mon Sep 17 00:00:00 2001 From: 0xrsydn Date: Wed, 4 Nov 2026 21:05:59 +0700 Subject: [PATCH] fix(changelog): one spelling per version, and check before replacing `release 00.5.0` passed the newer-version guard (its key equals 0.5.0's) and the exact-string duplicate check, then wrote `## [00.5.0]` and deleted the entries before `check` rejected the file (Pullfrog on #82). - Versions take no leading zeros, in `release`, `notes` and the headings `check` accepts. - `release` checks the file it is about to write and changes nothing unless it passes, so any other bad result also leaves CHANGELOG.md and the entries as they were. Claude-Session: https://claude.ai/code/session_01RqPPVeVDrnLoP7NUZmLRxY --- scripts/changelog-test.sh | 7 +++++++ scripts/changelog.sh | 21 +++++++++++++++------ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/scripts/changelog-test.sh b/scripts/changelog-test.sh index e079085..2b132f6 100644 --- a/scripts/changelog-test.sh +++ b/scripts/changelog-test.sh @@ -102,6 +102,10 @@ reset printf '\n## 0.3.0\n' >>"$work/CHANGELOG.md" expect_check_error "a malformed version heading" 'version headings read' +reset +printf '\n## [0.03.0] - 2026-01-01\n' >>"$work/CHANGELOG.md" +expect_check_error "a version with a leading zero" 'version headings read' + # --- release ----------------------------------------------------------------- # b-feature.md cites a lower PR than a-fix.md, so it comes first. @@ -193,6 +197,9 @@ if err="$(changelog release 0.4.9 2026-11-06 2>&1)"; then elif ! grep -q "older than 0.5.0" <<<"$err"; then fail "release of an older version said: ${err}" fi +if changelog release 00.5.0 2026-11-06 >/dev/null 2>&1; then + fail "release accepted a version with a leading zero" +fi [[ -f "$work/changes/unreleased/again.md" ]] || fail "a refused release deleted the entries" diff -q "$expected" "$work/CHANGELOG.md" >/dev/null || fail "a refused release changed CHANGELOG.md" rm "$work/changes/unreleased/again.md" diff --git a/scripts/changelog.sh b/scripts/changelog.sh index e45acee..c0cf89c 100755 --- a/scripts/changelog.sh +++ b/scripts/changelog.sh @@ -17,6 +17,8 @@ root="${CHANGELOG_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}" changelog="${root}/CHANGELOG.md" unreleased="${root}/changes/unreleased" repo_url="https://github.com/0xrsydn/idx-cli" +# X.Y.Z with no leading zeros, so each version has one spelling. +version_re='^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$' # Sections in the order a release lists them. sections=( @@ -96,13 +98,15 @@ check_summary() { ' "$file" >&2 } +# CHANGELOG.md, or the file given (a release checks what it is about to write). check_changelog() { - [[ -f "$changelog" ]] || die "missing ${changelog}" - awk -v file="$changelog" ' + local file="${1:-$changelog}" + [[ -f "$file" ]] || die "missing ${file}" + awk -v file="$file" ' /^```/ { fence = !fence } fence || !/^## / { next } { - if ($0 !~ /^## \[[0-9]+\.[0-9]+\.[0-9]+\] - [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]$/) { + if ($0 !~ /^## \[(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\] - [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]$/) { printf "%s:%d: version headings read \"## [X.Y.Z] - YYYY-MM-DD\"\n", file, NR; bad = 1; next } version = substr($2, 2, length($2) - 2) @@ -116,7 +120,7 @@ check_changelog() { last = key } END { exit bad } - ' "$changelog" >&2 + ' "$file" >&2 } cmd_check() { @@ -180,7 +184,7 @@ section_body() { cmd_release() { local version="${1:-}" date="${2:-}" - [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || die "release takes a version such as 0.5.0" + [[ "$version" =~ $version_re ]] || die "release takes a version such as 0.5.0, without leading zeros" if [[ -z "$date" ]]; then date="$(date -u +%Y-%m-%d)" fi @@ -229,6 +233,11 @@ cmd_release() { { print } END { if (!done) { print ""; while ((getline line < section) > 0) print line } } ' "$changelog" >"${out}.full" + # Nothing is replaced or deleted unless the result passes check. + if ! check_changelog "${out}.full"; then + rm -f "$out" "${out}.full" + die "the new CHANGELOG.md would not pass check; nothing was changed" + fi mv "${out}.full" "$changelog" rm -f "$out" "${files[@]}" "${unreleased}/summary.md" printf 'changelog: %s released from %d entries; review CHANGELOG.md, then commit\n' \ @@ -240,7 +249,7 @@ cmd_release() { cmd_notes() { local version="${1:-}" version="${version#v}" - [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || die "notes takes a version such as 0.5.0" + [[ "$version" =~ $version_re ]] || die "notes takes a version such as 0.5.0, without leading zeros" [[ -f "$changelog" ]] || die "missing ${changelog}" awk -v want="$version" ' /^```/ && inside { fence = !fence }