Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ DIRWORK := $(shell pwd -P)

.PHONY: all clean install lint format pull assets rules html pdf epub force
.PHONY: check check-rules check-rules-duplicates check-rules-incorrects
.PHONY: next-rule-id watch
.PHONY: next-rule-id watch changelog

all: clean html rules
clean:
Expand Down Expand Up @@ -51,6 +51,9 @@ next-rule-id:
"RULE_IDS=($$(grep -rh "^.*\[#[0-9]\{1,5\}.*$$" $(DIRCONTENTS) | sort -r))"; \
echo $$(($$(echo $${RULE_IDS[0]} | tr -d '\[' | tr -d '\]' | tr -d '#') + 1));

changelog:
$(DIRSCRIPTS)/changelog.sh --update

assets:
mkdir -p $(DIRBUILDS);
cp -r assets $(DIRBUILDS)/;
Expand Down
42 changes: 42 additions & 0 deletions scripts/changelog.prompt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
Write a changelog highlighting ONLY the rule changes based on the git commit messages and diffs provided.
Formatting, infrastructure, or similar non-rule changes should be omitted.

The rule numbers should always be referenced using angle brackets. For example, rule 211 should be written as <<211>>.

At the end of every changelog row, you should add a link to the related Pull Request in AsciiDoc format, like so: ^https://github.com/zalando/restful-api-guidelines/pull/<PR_NUMBER>[#PR_NUMBER]^

If there is no relevant Pull Request, you should link to a commit in asciidoc format instead, like so: ^https://github.com/zalando/restful-api-guidelines/commit/<COMMIT_HASH>[SHORT_HASH]^

Example changelog:

* `2021-12-09`: event id must not change in retry situations when producers <<211>>.
* `2021-11-24`: restructuring of the document and some rules.
* `2021-10-18`: new rule <<244>>.
* `2021-10-12`: improve clarity on {PATCH} usage in rule <<148>>.
* `2021-08-24`: improve clarity on {PUT} usage in rule <<148>>.
* `2021-08-24`: only use codes registered via IANA in rule <<243>>.
* `2021-08-17`: update formats per OpenAPI 3.1 in <<238>>.
* `2021-06-22`: <<238>> changed from {SHOULD} to {MUST}; consistency for rules around standards for data.
* `2021-06-03`: <<104>> with clear distinction of OpenAPI security schemes, favoring `bearer` to `oauth2`.
* `2021-06-01`: resolve uncertainties around 'occurred_at' semantics of <<event-metadata, event metadata>>.
* `2021-05-25`: <<172>> with <<114, API endpoint versioning>> as only custom media type usage exception.
* `2021-05-05`: define usage on resource-ids in {PUT} and {POST} in <<148>>.
* `2021-04-29`: improve clarity of <<133>>.
* `2021-03-19`: clarity on <<167>>.
* `2021-03-15`: <<242>> changed from {SHOULD} to {MUST}; improve clarity around <<203, event ordering>>.
* `2021-03-19`: best practice section <<cursor-based-pagination>>
* `2021-02-16`: define how to reference models outside the api in <<234>>.
* `2021-02-15`: improve guideline <<176>> -- clients must be prepared to not receive problem return objects.
* `2021-01-19`: more details for {GET-with-Body} and {DELETE-with-Body} (<<148>>).
* `2020-09-29`: include models for headers to be included by reference in API definitions (<<183>>)
* `2020-09-08`: add exception for legacy host names to <<224>>
* `2020-08-25`: change <<240>> from {MUST} to {SHOULD}, explain exceptions
* `2020-08-25`: add exception for `self` to <<143>> and <<134>>.
* `2020-08-24`: change "{MUST} avoid trailing slashes" to <<136>>.
* `2020-08-20`: change <<183>> from {MUST} to {SHOULD}, mention gateway-specific headers (which are not part of the public API).
* `2020-06-30`: add details to <<114>>
* `2020-05-19`: new sections about DELETE with query parameters and {DELETE-with-Body} in <<148>>.
* `2020-02-06`: new rule <<241>>
* `2020-02-05`: add Sunset header, clarify deprecation producedure (<<185>>, <<186>>, <<187>>, <<188>>, <<189>>, <<190>>, <<191>>)
* `2020-01-21`: new rule <<240>> (as MUST, changed later to SHOULD)
* `2020-01-15`: change "Warning" to "Deprecation" header in <<189>>, <<190>>.
177 changes: 177 additions & 0 deletions scripts/changelog.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
#!/bin/bash

# Generate changelog entries from git history using AI analysis
# Usage: ./changelog.sh [OPTIONS] [YEAR|DATE]
#
# By default (no arguments):
# - Detects latest changelog entry date from chapters/changelog.adoc
# - Generates entries for commits since that date
# - Outputs filtered changelog entries only
#
# Options:
# --raw Show full AI analysis output instead of filtered entries
# --update Write new entries to chapters/changelog.adoc (prepend to rule-changes section)
# --since DATE Use explicit date (format: YYYY-MM-DD) instead of auto-detecting
# --year YEAR Generate for a specific year (format: YYYY)
#
# Examples:
# ./changelog.sh # Auto-detect and generate since last entry
# ./changelog.sh --raw # Auto-detect with full output
# ./changelog.sh --update # Auto-detect and update the changelog file
# ./changelog.sh --since 2025-01-01 # Generate since specific date
# ./changelog.sh --year 2025 # Generate for entire year 2025
# ./changelog.sh 2025 --raw # Legacy: year as positional argument with --raw

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(dirname "$SCRIPT_DIR")"
PROMPT_FILE="$SCRIPT_DIR/changelog.prompt"
CHANGELOG_FILE="$REPO_ROOT/chapters/changelog.adoc"

# Parse arguments
MODE="auto" # auto, year, since
PARAM=""
RAW_OUTPUT=false
UPDATE_FILE=false

# Handle legacy positional arguments first
if [[ ${#@} -gt 0 ]] && [[ "$1" =~ ^[0-9]{4}$ ]]; then
MODE="year"
PARAM="$1"
shift
fi

# Parse remaining options
while [[ ${#@} -gt 0 ]]; do
case "$1" in
--raw)
RAW_OUTPUT=true
;;
--update)
UPDATE_FILE=true
;;
--year)
MODE="year"
PARAM="${2:-}"
if [[ -z "$PARAM" ]]; then
echo "Error: --year requires a year argument (YYYY)" >&2
exit 1
fi
shift
;;
--since)
MODE="since"
PARAM="${2:-}"
if [[ -z "$PARAM" ]]; then
echo "Error: --since requires a date argument (YYYY-MM-DD)" >&2
exit 1
fi
shift
;;
*)
echo "Error: Unknown option: $1" >&2
exit 1
;;
esac
shift
done

# Check dependencies
if ! command -v zllm &> /dev/null; then
echo "Error: 'zllm' command not found. Please install zllm to use this script." >&2
exit 1
fi

if [[ ! -f "$PROMPT_FILE" ]]; then
echo "Error: Prompt file not found: $PROMPT_FILE" >&2
exit 1
fi

# Determine the date range for git log
cd "$REPO_ROOT"

case "$MODE" in
auto)
# Extract latest date from changelog.adoc
if [[ ! -f "$CHANGELOG_FILE" ]]; then
echo "Error: Changelog file not found: $CHANGELOG_FILE" >&2
exit 1
fi

# Look for first date entry after "== Rule Changes" section
LATEST_DATE=$(grep -oP "^\* \`\K[0-9]{4}-[0-9]{2}-[0-9]{2}" "$CHANGELOG_FILE" | head -1)

if [[ -z "$LATEST_DATE" ]]; then
echo "Error: Could not find any date entries in changelog" >&2
exit 1
fi

echo "Latest changelog entry: $LATEST_DATE" >&2
AFTER_DATE="$LATEST_DATE"
;;
year)
if ! [[ "$PARAM" =~ ^[0-9]{4}$ ]]; then
echo "Error: Invalid year format. Expected YYYY, got: $PARAM" >&2
exit 1
fi
AFTER_DATE="$PARAM-01-01"
BEFORE_DATE="$PARAM-12-31"
;;
since)
if ! [[ "$PARAM" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then
echo "Error: Invalid date format. Expected YYYY-MM-DD, got: $PARAM" >&2
exit 1
fi
AFTER_DATE="$PARAM"
;;
esac

# Get git log with diffs
if [[ -n "${BEFORE_DATE:-}" ]]; then
GIT_LOG=$(git log -p --after="$AFTER_DATE" --before="$BEFORE_DATE")
else
GIT_LOG=$(git log -p --after="$AFTER_DATE")
fi

if [[ -z "$GIT_LOG" ]]; then
echo "No commits found since $AFTER_DATE" >&2
exit 0
fi

# Pipe prompt and git log through zllm
OUTPUT=$(cat "$PROMPT_FILE" <(echo) <(echo "$GIT_LOG") | zllm)

# Extract changelog entries (lines starting with '*')
ENTRIES=$(echo "$OUTPUT" | grep '^\*' || true)

# Output or update
if [[ "$RAW_OUTPUT" == true ]]; then
echo "$OUTPUT"
elif [[ "$UPDATE_FILE" == true ]]; then
if [[ -z "$ENTRIES" ]]; then
echo "No changelog entries generated. Not updating file." >&2
exit 0
fi

# Create temporary file with new entries inserted after "== Rule Changes" header
# Split file into: header + new entries + rest
HEADER_LINE=$(grep -n "^== Rule Changes$" "$CHANGELOG_FILE" | cut -d: -f1)

{
# Read file up to (but not including) "== Rule Changes"
head -n "$((HEADER_LINE - 1))" "$CHANGELOG_FILE"
# Add the header and blank line
echo "== Rule Changes"
echo
# Add new entries
echo "$ENTRIES"
# Add existing entries (skip "== Rule Changes" line and the blank line after it)
tail -n +"$((HEADER_LINE + 2))" "$CHANGELOG_FILE"
} > "$CHANGELOG_FILE.tmp"

mv "$CHANGELOG_FILE.tmp" "$CHANGELOG_FILE"
echo "Updated $CHANGELOG_FILE with new entries" >&2
else
echo "$ENTRIES"
fi