|
1 | | -#!/bin/bash |
| 1 | +#!/usr/bin/env bash |
| 2 | +set -euo pipefail |
2 | 3 |
|
3 | 4 | SCRIPT_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. |
| 5 | +CHANGELOG="${1:-$SCRIPT_ROOT/CHANGELOG.md}" |
4 | 6 |
|
5 | | -# Define filename |
6 | | -filename="$SCRIPT_ROOT/CHANGELOG.md" |
7 | | - |
8 | | -# Check if file exists |
9 | | -if [[ ! -f "$filename" ]]; then |
10 | | - echo "Error: $filename does not exist." |
| 7 | +if [[ ! -f "$CHANGELOG" ]]; then |
| 8 | + echo "Error: $CHANGELOG not found" >&2 |
11 | 9 | exit 1 |
12 | 10 | fi |
13 | 11 |
|
14 | | -# Storing the version to be checked |
15 | | -mapfile -t versions < <(awk '/## History/{flag=1;next}/## /{flag=0}flag' "$filename" | grep -o '\[[^]]*\]' | grep -v "v1." | sed 's/[][]//g') |
16 | | - |
17 | | -# Define a function to extract and sort sections |
18 | | -function extract_and_check() { |
19 | | - local section=$1 |
20 | | - local content_block=$2 |
21 | | - local content=$(awk "/### $section/{flag=1;next}/### /{flag=0}flag" <<< "$content_block" | grep '^- \*\*') |
22 | | - |
23 | | - # Skip if content does not exist |
24 | | - if [[ -z "$content" ]]; then |
25 | | - return |
26 | | - fi |
27 | | - |
28 | | - # Separate and sort the **General**: lines |
29 | | - local sorted_general_lines=$(echo "$content" | grep '^- \*\*General\*\*:' | LC_ALL=en_US sort --ignore-case) |
30 | | - |
31 | | - # Sort the remaining lines |
32 | | - local sorted_content=$(echo "$content" | grep -v '^- \*\*General\*\*:' | LC_ALL=en_US sort --ignore-case) |
33 | | - |
34 | | - # Check if sorted_general_lines is not empty, then concatenate |
35 | | - if [[ -n "$sorted_general_lines" ]]; then |
36 | | - sorted_content=$(printf "%s\n%s" "$sorted_general_lines" "$sorted_content") |
37 | | - fi |
38 | | - |
39 | | - # Check pattern and throw error if wrong pattern found |
40 | | - while IFS= read -r line; do |
41 | | - echo "Error: Wrong pattern found in section: $section , line: $line" |
42 | | - exit 1 |
43 | | - done < <(grep -Pv '^(-\s\*\*[^*]+\*\*: .*\(\[#([\d|TODO]+)\]\(https:\/\/github\.com\/kedacore\/(http-add-on|charts|governance)\/(pull|issues|discussions)\/\2\)(?:\|\[#(\d+)\]\(https:\/\/github\.com\/kedacore\/(http-add-on|charts|governance)\/(pull|issues|discussions)\/(?:\5)\)){0,}\))$' <<< "$content") |
44 | | - |
45 | | - if [ "$content" != "$sorted_content" ]; then |
46 | | - echo "Error: Section: $section is not sorted correctly. Correct order:" |
47 | | - echo "$sorted_content" |
48 | | - exit 1 |
49 | | - fi |
50 | | -} |
| 12 | +# Format: - **Component**: description ([#NUM](URL)|...) where NUM matches URL path |
| 13 | +LINK='\[#([0-9]+|TODO)\]\(https://github\.com/kedacore/[^/]+/(pull|issues|discussions)/([0-9]+|TODO)\)' |
| 14 | +LINE_PATTERN="^- \\*\\*[^*]+\\*\\*: .+\\($LINK(\\|$LINK)*\\)\$" |
51 | 15 |
|
| 16 | +SECTIONS=("Breaking Changes" "New" "Improvements" "Fixes" "Deprecations" "Other") |
52 | 17 |
|
53 | | -# Extract release sections, including "Unreleased", and check them |
54 | | -for version in "${versions[@]}"; do |
55 | | - release_content=$(awk "/## $version/{flag=1;next}/## v[0-9\.]+/{flag=0}flag" "$filename") |
| 18 | +errors=0 |
56 | 19 |
|
| 20 | +# Get content between two markdown headers |
| 21 | +get_section() { |
| 22 | + local version="$1" |
| 23 | + local section="$2" |
| 24 | + # Match until next ## header (v* or Unreleased) or EOF |
| 25 | + sed -n "/^## $version\$/,/^## [vU]/p" "$CHANGELOG" | sed -n "/^### $section\$/,/^### /p" | grep '^- \*\*' || true |
| 26 | +} |
57 | 27 |
|
58 | | - if [[ -z "$release_content" ]]; then |
59 | | - echo "No content found for $version Skipping." |
60 | | - continue |
61 | | - fi |
| 28 | +# Validate [#NUM] matches URL path number (skips TODO links) |
| 29 | +validate_link_numbers() { |
| 30 | + local line="$1" link link_num url_num valid=0 |
| 31 | + for link in $(echo "$line" | grep -oE '\[#[0-9]+\]\([^)]+\)'); do |
| 32 | + link_num=$(echo "$link" | grep -oE '\[#[0-9]+\]' | tr -d '[]#') |
| 33 | + url_num=$(echo "$link" | grep -oE '(pull|issues|discussions)/[0-9]+' | grep -oE '[0-9]+$' || true) |
| 34 | + if [[ -z "$url_num" ]]; then |
| 35 | + echo "could not extract URL number from: $link" >&2 |
| 36 | + valid=1 |
| 37 | + elif [[ "$link_num" != "$url_num" ]]; then |
| 38 | + echo "link [#$link_num] does not match URL number $url_num" >&2 |
| 39 | + valid=1 |
| 40 | + fi |
| 41 | + done |
| 42 | + return $valid |
| 43 | +} |
62 | 44 |
|
63 | | - echo "Checking section: $version" |
| 45 | +# Sort: General lines first, then rest alphabetically |
| 46 | +sort_section() { |
| 47 | + local input general_lines other_lines |
| 48 | + input=$(cat) |
| 49 | + general_lines=$(echo "$input" | grep '^- \*\*General\*\*:' | LC_ALL=C sort -f || true) |
| 50 | + other_lines=$(echo "$input" | grep -v '^- \*\*General\*\*:' | LC_ALL=C sort -f || true) |
| 51 | + # Output non-empty parts, avoiding extra newlines |
| 52 | + [[ -n "$general_lines" ]] && echo "$general_lines" |
| 53 | + [[ -n "$other_lines" ]] && echo "$other_lines" |
| 54 | + true |
| 55 | +} |
64 | 56 |
|
65 | | - # Separate content into different sections and check sorting for each release |
66 | | - extract_and_check "New" "$release_content" |
67 | | - extract_and_check "Experimental" "$release_content" |
68 | | - extract_and_check "Improvements" "$release_content" |
69 | | - extract_and_check "Fixes" "$release_content" |
70 | | - extract_and_check "Deprecations" "$release_content" |
71 | | - extract_and_check "Other" "$release_content" |
| 57 | +# Get versions from History section |
| 58 | +versions=$(sed -n '/^## History/,/^## /p' "$CHANGELOG" | grep -o '\[[^]]*\]' | tr -d '[]' || true) |
72 | 59 |
|
| 60 | +if [[ -z "$versions" ]]; then |
| 61 | + echo "Error: No versions found in ## History section" >&2 |
| 62 | + exit 1 |
| 63 | +fi |
| 64 | + |
| 65 | +for version in $versions; do |
| 66 | + echo "Checking: $version" |
| 67 | + |
| 68 | + for section in "${SECTIONS[@]}"; do |
| 69 | + content=$(get_section "$version" "$section") |
| 70 | + [[ -z "$content" ]] && continue |
| 71 | + |
| 72 | + # Check format and link numbers |
| 73 | + while IFS= read -r line; do |
| 74 | + if ! echo "$line" | grep -qE "$LINE_PATTERN"; then |
| 75 | + echo " Error: [$section] Invalid format: $line" >&2 |
| 76 | + errors=1 |
| 77 | + elif ! validate_link_numbers "$line"; then |
| 78 | + echo " Error: [$section] $line" >&2 |
| 79 | + errors=1 |
| 80 | + fi |
| 81 | + done <<< "$content" |
| 82 | + |
| 83 | + # Check sorting |
| 84 | + sorted=$(echo "$content" | sort_section) |
| 85 | + if [[ "$content" != "$sorted" ]]; then |
| 86 | + echo " Error: [$section] Not sorted. Expected:" >&2 |
| 87 | + echo "$sorted" | sed 's/^/ /' >&2 |
| 88 | + errors=1 |
| 89 | + fi |
| 90 | + done |
73 | 91 | done |
| 92 | + |
| 93 | +if [[ $errors -eq 0 ]]; then |
| 94 | + echo "OK" |
| 95 | +else |
| 96 | + echo "Validation failed" >&2 |
| 97 | +fi |
| 98 | + |
| 99 | +exit $errors |
0 commit comments