Files
small-packages/luci-theme-footstrap/tools/release-notes.sh
T
2026-09-20 01:22:33 +08:00

126 lines
4.6 KiB
Bash
Executable File

#!/bin/sh
# Build the GitHub-release body from the CHANGELOG — generated, never hand-written, so the changelog
# stays the single source.
#
# Every bullet is `- **one-line effect.** then the rationale` (docs/releasing.md): the bold lead is
# what a release reader wants, the rationale is for maintainers. So the notes are those leads,
# grouped under their `###` headers — which means a bullet with NO bold lead is silently omitted,
# and an empty section prints no header.
#
# CHANGELOG.md is primary; the CHANGELOG_ru.md mirror's summary is appended under a divider.
#
# Usage: release-notes.sh <version> [changelog-file], version without the leading v.
# Prints to stdout. Pure sh and awk, so the release job needs no node.
set -eu
ver="${1:?usage: release-notes.sh <version> [changelog]}"
changelog="${2:-CHANGELOG.md}"
# the RU mirror sits beside it: CHANGELOG.md -> CHANGELOG_ru.md
changelog_ru="${changelog%.md}_ru.md"
[ -f "$changelog" ] || { echo "no such changelog: $changelog" >&2; exit 1; }
# extract(file): the bold lead of every bullet in the [ver] section, grouped under its `###`
# headers (rationale and empty sections dropped). Same logic for either language. Prints
# nothing if the section is absent.
extract() {
awk -v ver="$ver" '
# Print the finished bold title, emitting its section header first the one time a
# header is still pending — so an empty section never prints a lone header.
function flush( t) {
if (!collecting) return
t = title
gsub(/[ \t]+/, " ", t); sub(/^ +/, "", t); sub(/ +$/, "", t)
if (pending_hdr != "") {
if (started) print "" # blank line between sections
print pending_hdr; pending_hdr = ""; started = 1
}
print "- " t
collecting = 0; title = ""
}
# enter the target version section; leave at the next "## [" heading
$0 ~ ("^## \\[" ver "\\]") { insec = 1; next }
insec && /^## \[/ { insec = 0 }
!insec { next }
# a bold title that ran onto the next line(s): accumulate until the closing **
collecting {
e = index($0, "**")
if (e > 0) { title = title " " substr($0, 1, e - 1); flush() }
else title = title " " $0
next
}
/^### / { flush(); pending_hdr = $0; next } # header, held until a bullet prints
/^- \*\*/ { # bullet: keep only its **bold lead**
rest = substr($0, index($0, "**") + 2)
e = index(rest, "**")
collecting = 1
if (e > 0) { title = substr(rest, 1, e - 1); flush() } # closes on this line
else title = rest # continues below
next
}
# any other line (rationale continuation, blank) is dropped
END { flush() }
' "$1"
}
# A tag whose changelog section does not exist is a FAILED release, not a thin one. This is
# the only enforcement of docs/releasing.md's rule (rename [Unreleased], commit that, then tag THAT
# commit): warning to stderr and exiting 0 published a release page reading "See the
# CHANGELOG" for a version the changelog had never heard of.
summary="$(extract "$changelog")"
if [ -z "$summary" ]; then
echo "error: no '## [$ver]' section in $changelog." >&2
echo " Tag a commit that already carries its own changelog entry:" >&2
echo " rename [Unreleased] -> [$ver], commit, then tag that commit." >&2
exit 1
fi
# The RU mirror must carry the section too. A mirror that lags is worse than none: the release
# page would show the English half only and nobody could tell which file was stale. Both are
# edited in the same commit.
summary_ru=""
if [ -f "$changelog_ru" ]; then
summary_ru="$(extract "$changelog_ru")"
if [ -z "$summary_ru" ]; then
echo "error: no '## [$ver]' section in $changelog_ru (the EN file has one)." >&2
echo " CHANGELOG.md and CHANGELOG_ru.md are kept in lockstep." >&2
exit 1
fi
fi
cat <<EOF
## luci-theme-footstrap v$ver
$summary
EOF
if [ -n "$summary_ru" ]; then
cat <<EOF
---
$summary_ru
EOF
fi
# THE RELEASE ASSET, not raw.githubusercontent.com/main. Two reasons, both already written down
# elsewhere and both undone by pointing at the branch: stage-release.sh attaches and signs
# install.sh precisely because raw.githubusercontent is rate-limited for unauthenticated callers —
# so the user whose IP has run out of budget cannot fetch the installer meant to rescue them (issue
# #17) — and a release page that links `main` hands a reader of an OLD release whatever the branch
# says today, which is not the script that release was tested with.
cat <<EOF
<details><summary>Install</summary>
One-liner (auto-detects apk/ipk):
\`\`\`sh
wget -qO- https://github.com/VizzleTF/luci-theme-footstrap/releases/latest/download/install.sh | sh
\`\`\`
</details>
EOF