mirror of
https://github.com/0xrsydn/idx-cli.git
synced 2026-11-07 09:45:36 +00:00
docs: keep a CHANGELOG.md, with one entry file per PR
Release notes were written by hand on GitHub at tag time, so the contents of a stacked release had to be pieced together from every PR, and output changes that scripts and agents rely on were easy to miss. - CHANGELOG.md backfills every published release (0.2.0 to 0.4.0) from its GitHub notes, verbatim. - A PR adds changes/unreleased/<branch-name>.md instead of editing CHANGELOG.md, so stacked PRs never conflict. changes/README.md gives the sections, including Output Changes for JSON, MCP and error-code changes. - scripts/changelog.sh: `check` validates entries and CHANGELOG.md, `release X.Y.Z` folds the entries into a new section (ordered by PR), and `notes X.Y.Z` prints a section as GitHub release notes. - The release workflow sets the GitHub notes from the tag's section. - New flake check `changelog` runs scripts/changelog-test.sh; both scripts are shellchecked. - Entries for 0.5.0: #73, #74, #75, #78, #79, #80, #81 and this PR. Claude-Session: https://claude.ai/code/session_01RqPPVeVDrnLoP7NUZmLRxY
This commit is contained in:
parent
84c3255716
commit
809966925e
21 changed files with 942 additions and 13 deletions
223
scripts/changelog-test.sh
Normal file
223
scripts/changelog-test.sh
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
#!/usr/bin/env bash
|
||||
# Tests scripts/changelog.sh against a scratch checkout, then checks the real
|
||||
# CHANGELOG.md and entries.
|
||||
set -Eeuo pipefail
|
||||
|
||||
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
script="${repo_root}/scripts/changelog.sh"
|
||||
work="$(mktemp -d "${TMPDIR:-/tmp}/idx-changelog-test.XXXXXX")"
|
||||
trap 'rm -rf "$work"' EXIT
|
||||
failures=0
|
||||
|
||||
fail() {
|
||||
printf 'FAIL: %s\n' "$*" >&2
|
||||
failures=$((failures + 1))
|
||||
}
|
||||
|
||||
changelog() {
|
||||
CHANGELOG_ROOT="$work" bash "$script" "$@"
|
||||
}
|
||||
|
||||
# A checkout with one released version and no entries.
|
||||
reset() {
|
||||
rm -rf "${work:?}"/*
|
||||
mkdir -p "$work/changes/unreleased"
|
||||
cat >"$work/CHANGELOG.md" <<'EOF'
|
||||
# Changelog
|
||||
|
||||
Intro.
|
||||
|
||||
## [0.4.0] - 2026-10-19
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- An old fix. (#1)
|
||||
EOF
|
||||
}
|
||||
|
||||
entry() {
|
||||
cat >"$work/changes/unreleased/$1"
|
||||
}
|
||||
|
||||
expect_check_error() {
|
||||
local what="$1" pattern="$2" err
|
||||
if err="$(changelog check 2>&1)"; then
|
||||
fail "check accepted ${what}"
|
||||
elif ! grep -q -- "$pattern" <<<"$err"; then
|
||||
fail "check on ${what} said: ${err}"
|
||||
fi
|
||||
}
|
||||
|
||||
# --- check -----------------------------------------------------------------
|
||||
|
||||
reset
|
||||
changelog check >/dev/null || fail "check refused a clean checkout"
|
||||
|
||||
entry bad.md <<'EOF'
|
||||
### Fixes
|
||||
|
||||
- x (#2)
|
||||
EOF
|
||||
expect_check_error "an unknown section" 'unknown section "Fixes"'
|
||||
|
||||
reset
|
||||
entry bad.md <<'EOF'
|
||||
- a bullet before any heading (#2)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- x (#2)
|
||||
EOF
|
||||
expect_check_error "text before a heading" "text before the first"
|
||||
|
||||
reset
|
||||
entry bad.md <<'EOF'
|
||||
### Bug Fixes
|
||||
|
||||
### New Features
|
||||
|
||||
- x (#2)
|
||||
EOF
|
||||
expect_check_error "an empty section" 'section "Bug Fixes" is empty'
|
||||
|
||||
reset
|
||||
entry bad.md <<'EOF'
|
||||
## Bug Fixes
|
||||
|
||||
- x (#2)
|
||||
EOF
|
||||
expect_check_error "a level-2 heading" 'use "### <section>" headings only'
|
||||
|
||||
reset
|
||||
entry summary.md <<'EOF'
|
||||
### Not here
|
||||
EOF
|
||||
expect_check_error "a heading in the summary" "the summary takes no headings"
|
||||
|
||||
reset
|
||||
printf '\n## [0.5.0] - 2026-11-05\n\n- newer below older\n' >>"$work/CHANGELOG.md"
|
||||
expect_check_error "versions out of order" "is not older than the version above it"
|
||||
|
||||
reset
|
||||
printf '\n## 0.3.0\n' >>"$work/CHANGELOG.md"
|
||||
expect_check_error "a malformed version heading" 'version headings read'
|
||||
|
||||
# --- release -----------------------------------------------------------------
|
||||
|
||||
# b-feature.md cites a lower PR than a-fix.md, so it comes first.
|
||||
reset
|
||||
entry b-feature.md <<'EOF'
|
||||
### New Features
|
||||
|
||||
- Feature B, which spans
|
||||
two lines. (#9)
|
||||
|
||||
### Output Changes
|
||||
|
||||
- **Breaking:** field `x` is gone. (#9)
|
||||
EOF
|
||||
entry a-fix.md <<'EOF'
|
||||
### Bug Fixes
|
||||
|
||||
- Fix A. (#10)
|
||||
|
||||
### New Features
|
||||
|
||||
- Feature A, with an example:
|
||||
|
||||
```bash
|
||||
# a comment, not a heading
|
||||
idx stocks quote BBCA
|
||||
```
|
||||
EOF
|
||||
entry summary.md <<'EOF'
|
||||
|
||||
**Upgrade from 0.4.0**: it fixes A.
|
||||
|
||||
EOF
|
||||
changelog release 0.5.0 2026-11-05 >/dev/null || fail "release failed"
|
||||
|
||||
expected="$work/expected.md"
|
||||
cat >"$expected" <<'EOF'
|
||||
# Changelog
|
||||
|
||||
Intro.
|
||||
|
||||
## [0.5.0] - 2026-11-05
|
||||
|
||||
**Upgrade from 0.4.0**: it fixes A.
|
||||
|
||||
### New Features
|
||||
|
||||
- Feature B, which spans
|
||||
two lines. (#9)
|
||||
- Feature A, with an example:
|
||||
|
||||
```bash
|
||||
# a comment, not a heading
|
||||
idx stocks quote BBCA
|
||||
```
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix A. (#10)
|
||||
|
||||
### Output Changes
|
||||
|
||||
- **Breaking:** field `x` is gone. (#9)
|
||||
|
||||
**Full Changelog**: https://github.com/0xrsydn/idx-cli/compare/v0.4.0...v0.5.0
|
||||
|
||||
## [0.4.0] - 2026-10-19
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- An old fix. (#1)
|
||||
EOF
|
||||
diff -u "$expected" "$work/CHANGELOG.md" || fail "release wrote a different CHANGELOG.md"
|
||||
if find "$work/changes/unreleased" -name '*.md' | grep -q .; then
|
||||
fail "release left entries behind"
|
||||
fi
|
||||
changelog check >/dev/null || fail "check refused the released CHANGELOG.md"
|
||||
|
||||
entry again.md <<'EOF'
|
||||
### Bug Fixes
|
||||
|
||||
- x (#12)
|
||||
EOF
|
||||
if changelog release 0.5.0 2026-11-06 >/dev/null 2>&1; then
|
||||
fail "release accepted a version already released"
|
||||
fi
|
||||
rm "$work/changes/unreleased/again.md"
|
||||
if changelog release 0.5.1 >/dev/null 2>&1; then
|
||||
fail "release accepted no entries"
|
||||
fi
|
||||
|
||||
# --- notes -------------------------------------------------------------------
|
||||
|
||||
notes="$(changelog notes v0.5.0)"
|
||||
[[ "$(head -1 <<<"$notes")" == "**Upgrade from 0.4.0**: it fixes A." ]] ||
|
||||
fail "notes do not open with the summary: $(head -1 <<<"$notes")"
|
||||
grep -qx '## New Features' <<<"$notes" || fail "notes did not raise section headings"
|
||||
grep -qx ' # a comment, not a heading' <<<"$notes" || fail "notes changed a code block"
|
||||
grep -q '0.4.0\] -' <<<"$notes" && fail "notes ran into the next version"
|
||||
[[ "$(tail -1 <<<"$notes")" == "**Full Changelog**: https://github.com/0xrsydn/idx-cli/compare/v0.4.0...v0.5.0" ]] ||
|
||||
fail "notes do not end with the compare link"
|
||||
[[ "$(changelog notes 0.4.0)" == "## Bug Fixes"$'\n\n'"- An old fix. (#1)" ]] ||
|
||||
fail "notes for the oldest version: $(changelog notes 0.4.0)"
|
||||
if changelog notes 0.9.9 >/dev/null 2>&1; then
|
||||
fail "notes accepted a version not in CHANGELOG.md"
|
||||
fi
|
||||
|
||||
# --- the real changelog --------------------------------------------------------
|
||||
|
||||
bash "$script" check >/dev/null || fail "the repository's changelog entries do not pass check"
|
||||
while read -r version; do
|
||||
[[ -n "$(bash "$script" notes "$version")" ]] || fail "no notes for ${version}"
|
||||
done < <(awk '/^## \[[0-9]/ { print substr($2, 2, length($2) - 2) }' "${repo_root}/CHANGELOG.md")
|
||||
|
||||
if ((failures > 0)); then
|
||||
printf '%d changelog test(s) failed\n' "$failures" >&2
|
||||
exit 1
|
||||
fi
|
||||
printf 'changelog tests passed\n'
|
||||
Loading…
Add table
Add a link
Reference in a new issue