diff --git a/.github/workflows/release-cut.yml b/.github/workflows/release-cut.yml index 1e6fcba0..1a779d0d 100644 --- a/.github/workflows/release-cut.yml +++ b/.github/workflows/release-cut.yml @@ -551,15 +551,19 @@ jobs: env: IS_PRERELEASE: ${{ steps.tag.outputs.is_prerelease }} RELEASE_TAG: ${{ steps.next.outputs.release_tag }} + REPO: ${{ github.repository }} + GH_TOKEN: ${{ github.token }} run: | set -euo pipefail notes_path="dist/release-notes-${RELEASE_TAG}.md" entry_path="$(mktemp)" missing_heading="## [${RELEASE_TAG#v}] - YYYY-MM-DD" + used_unreleased_fallback=false if ! node scripts/extract-changelog-entry.mjs --version "${RELEASE_TAG}" --file CHANGELOG.md > "${entry_path}"; then if [ "${IS_PRERELEASE}" = "true" ] && node scripts/extract-changelog-entry.mjs --version "Unreleased" --file CHANGELOG.md > "${entry_path}" 2>/dev/null; then echo "Using [Unreleased] changelog section for pre-release ${RELEASE_TAG}." + used_unreleased_fallback=true else rm -f "${entry_path}" "${notes_path}" echo "::error::Release notes generation failed: CHANGELOG entry missing for ${RELEASE_TAG}. Add heading '${missing_heading}' and retry release." @@ -571,9 +575,37 @@ jobs: exit 1 fi fi + + # The [Unreleased] fallback means the CHANGELOG was not stamped before + # the cut. Rewrite the section heading in the generated notes so the + # published release shows the version and date instead of a literal + # "[Unreleased]" — a cut can never ship an unstamped heading. The + # CHANGELOG.md file itself is stamped separately at release time. + if [ "${used_unreleased_fallback}" = "true" ]; then + stamped_heading="## [${RELEASE_TAG#v}] — $(date -u +%Y-%m-%d)" + stamped_path="$(mktemp)" + awk -v h="${stamped_heading}" \ + 'NR == 1 && /^## \[Unreleased\][[:space:]]*$/ { print h; next } { print }' \ + "${entry_path}" > "${stamped_path}" + mv "${stamped_path}" "${entry_path}" + fi + + # Resolve the previous release for a Full Changelog compare link. The + # current tag is not pushed yet, so the newest existing release is the + # predecessor; the select() guards re-runs after a partial release. + prev_tag="$(gh release list --repo "${REPO}" --limit 100 \ + --json tagName,createdAt \ + --jq 'sort_by(.createdAt) | reverse | map(.tagName) + | map(select(. != env.RELEASE_TAG)) | .[0] // empty' \ + 2>/dev/null || true)" + { echo "# ${RELEASE_TAG}" echo "" + if [ -n "${prev_tag}" ]; then + echo "**Full Changelog**: https://github.com/${REPO}/compare/${prev_tag}...${RELEASE_TAG}" + echo "" + fi cat "${entry_path}" } > "${notes_path}" rm -f "${entry_path}"