diff --git a/cliff.toml b/cliff.toml index c5bd82b..efbb0e2 100644 --- a/cliff.toml +++ b/cliff.toml @@ -8,12 +8,27 @@ breaking_always_bump_major = false # header, body, and footer keys are Tera templates # https://keats.github.io/tera/docs/#introduction +# +# The rule below is about the header template specifically, not about this +# file as a whole. Blank lines in the header are written as literal blank +# lines, never as trailing \n escapes: a TOML multi-line string already ends +# each source line with a newline, so an escape on top of that emits a second +# one. That doubling is invisible in the source and lands in the generated +# changelog as consecutive blank lines, which markdownlint rejects (MD012) in +# the changelog-autoupdate workflow. +# +# The body template is the opposite case. Its source lines are joined with +# trailing backslash continuations instead of emitting one output line each, +# so the \n escapes it uses to place newlines are deliberate. header = """ -\n -\n -# Changelog\n -All notable changes will be documented in this file. See [conventional commits](https://www.conventionalcommits.org) for commit guidelines.\n -The format is based on [Keep a Changelog](https://keepachangelog.com) and this project adheres to [Semantic Versioning](https://semver.org).\n + + +# Changelog + +All notable changes will be documented in this file. See [conventional commits](https://www.conventionalcommits.org) for commit guidelines. + +The format is based on [Keep a Changelog](https://keepachangelog.com) and this project adheres to [Semantic Versioning](https://semver.org). + """ body = """ diff --git a/tests/test-cliff-header.sh b/tests/test-cliff-header.sh new file mode 100755 index 0000000..c0d7891 --- /dev/null +++ b/tests/test-cliff-header.sh @@ -0,0 +1,104 @@ +#!/usr/bin/env bash +# Test script for the [changelog] header template in cliff.toml + +set -uo pipefail +TEST_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Load shared configurations +# shellcheck disable=SC1091 # Dynamic path via $TEST_SCRIPT_DIR +. "$TEST_SCRIPT_DIR/colors.sh" + +CLIFF_TOML="$TEST_SCRIPT_DIR/../cliff.toml" + +PASSED=0 +FAILED=0 + +pass() { + echo -e "${GREEN}✓${NC} $1" + ((PASSED++)) +} + +fail() { + echo -e "${RED}✗${NC} $1" + shift + for msg in "$@"; do + echo -e " ${YELLOW}$msg${NC}" + done + ((FAILED++)) +} + +# Render the header template the way TOML does, so the assertions below run +# against the text git-cliff actually writes. git-cliff is not installed on +# every machine that runs this suite (and not in CI at all), and the header is +# static text with no Tera expressions, so decoding it here is both sufficient +# and portable. +# +# TOML multi-line basic strings drop the newline right after the opening +# delimiter; every other source line contributes its text plus one newline, and +# a trailing \n escape contributes another newline on top of that. That second +# newline is easy to add by accident and invisible in the source, which is what +# MD012 below is guarding against. +render_header() { + awk ' + /^header = """$/ { in_block = 1; next } + in_block && /^"""$/ { exit } + in_block { + line = $0 + extra = 0 + while (sub(/\\n$/, "", line)) extra++ + if (line ~ /\\/) { + print "unsupported escape in header template: " $0 > "/dev/stderr" + exit 1 + } + print line + for (i = 0; i < extra; i++) print "" + } + ' "$1" +} + +if ! work="$(mktemp -d)"; then + echo "Error: could not create a temporary directory" >&2 + exit 1 +fi +trap 'rm -rf "$work"' EXIT INT TERM HUP +rendered="$work/header.md" + +if ! render_header "$CLIFF_TOML" >"$rendered"; then + fail "header template renders" "render_header could not decode the template" +elif [ ! -s "$rendered" ]; then + fail "header template renders" "Rendered header is empty; is the 'header = \"\"\"' block still there?" +else + pass "header template renders" + + # MD012/no-multiple-blanks. markdownlint runs over the generated changelog + # in the changelog-autoupdate workflow, and a header that trips this rule + # fails that job every week without ever touching a commit. + # + # markdownlint counts a whitespace-only line as blank, so this check and + # the trailing-blank count below have to as well; testing for an empty + # line alone would pass a header that markdownlint still rejects. + offenders="$(awk 'BEGIN { run = 0 } { if ($0 ~ /^[[:space:]]*$/) { run++; if (run > 1) print NR } else run = 0 }' "$rendered")" + if [ -n "$offenders" ]; then + # shellcheck disable=SC2086 # Word splitting turns the line list into args + fail "header has no consecutive blank lines (MD012)" \ + "Extra blank lines at rendered line(s): $(echo $offenders | tr ' ' ',')" \ + "Rendered header:" \ + "$(cat -v "$rendered")" + else + pass "header has no consecutive blank lines (MD012)" + fi + + # The header has to end with exactly one blank line so the first '## [' + # section that follows it is separated by one blank line, not glued on. + trailing_blanks="$(awk '{ if ($0 ~ /^[[:space:]]*$/) blanks++; else blanks = 0 } END { print blanks + 0 }' "$rendered")" + if [ "$trailing_blanks" -ne 1 ]; then + fail "header ends with a single blank line" \ + "Header ends with $trailing_blanks trailing blank line(s), expected 1" + else + pass "header ends with a single blank line" + fi +fi + +echo "" +echo -e "Results: ${GREEN}$PASSED passed${NC}, ${RED}$FAILED failed${NC}" +[ "$FAILED" -eq 0 ] diff --git a/tests/test-unit.sh b/tests/test-unit.sh index 7f916a2..05e8660 100755 --- a/tests/test-unit.sh +++ b/tests/test-unit.sh @@ -43,6 +43,7 @@ run_test "$TEST_SCRIPT_DIR/test-conventional-merge-commit.sh" "conventional-merg run_test "$TEST_SCRIPT_DIR/test-parse-version.sh" "release/parse-version.sh tests" run_test "$TEST_SCRIPT_DIR/test-bump-pins.sh" "release/bump-pins.sh tests" run_test "$TEST_SCRIPT_DIR/test-stamp-changelog.sh" "release/stamp-changelog.sh tests" +run_test "$TEST_SCRIPT_DIR/test-cliff-header.sh" "cliff.toml changelog header tests" # Summary echo "=========================================="