#!/usr/bin/env bash # Generate release notes using LLM analysis of git commits # Usage: ./scripts/generate-release-notes.sh [previous-tag] set -euo pipefail VERSION=${1:-} PREVIOUS_TAG=${2:-} if [ -z "$VERSION" ]; then echo "Usage: $0 [previous-tag]" echo "Example: $0 4.29.0 v4.28.0" exit 1 fi # Find previous tag if not specified if [ -z "$PREVIOUS_TAG" ]; then PREVIOUS_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") if [ -z "$PREVIOUS_TAG" ]; then echo "No previous tag found, using all commits" PREVIOUS_TAG=$(git rev-list --max-parents=0 HEAD) fi fi echo "Generating release notes for v${VERSION}..." echo "Analyzing commits since ${PREVIOUS_TAG}..." # Get commit log COMMIT_LOG=$(git log ${PREVIOUS_TAG}..HEAD --pretty=format:"%h %s" --no-merges) if [ -z "$COMMIT_LOG" ]; then echo "No commits found since ${PREVIOUS_TAG}" exit 1 fi # Count commits COMMIT_COUNT=$(echo "$COMMIT_LOG" | wc -l) echo "Found ${COMMIT_COUNT} commits" # Generate release notes using LLM API # Supports both OpenAI and Anthropic Claude # Set either OPENAI_API_KEY or ANTHROPIC_API_KEY if [ -n "${ANTHROPIC_API_KEY:-}" ]; then LLM_PROVIDER="anthropic" elif [ -n "${OPENAI_API_KEY:-}" ]; then LLM_PROVIDER="openai" else echo "Error: Either OPENAI_API_KEY or ANTHROPIC_API_KEY environment variable must be set" exit 1 fi echo "Using LLM provider: ${LLM_PROVIDER}" # Prepare prompt for LLM read -r -d '' PROMPT <&2 return 1 } response_type=$(echo "$response" | jq -r '.type // empty') if [ "$response_type" = "error" ]; then local message message=$(echo "$response" | jq -r '.error.message // "Unknown error"') echo "Anthropic API error: $message" >&2 return 1 fi content=$(echo "$response" | jq -r '.content[0].text // empty') if [ -z "$content" ] || [ "$content" = "null" ]; then echo "Anthropic API returned empty content: $response" >&2 return 1 fi printf '%s' "$content" } # Helper to call OpenAI (returns notes via stdout) generate_with_openai() { local response content error_msg response=$(curl -s https://api.openai.com/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer ${OPENAI_API_KEY}" \ -d @- <&2 return 1 } error_msg=$(echo "$response" | jq -r '.error.message? // empty') if [ -n "$error_msg" ]; then echo "OpenAI API error: $error_msg" >&2 return 1 fi content=$(echo "$response" | jq -r '.choices[0].message.content // empty') if [ -z "$content" ] || [ "$content" = "null" ]; then echo "OpenAI API returned empty content: $response" >&2 return 1 fi printf '%s' "$content" } # Call LLM API based on provider with graceful fallback RELEASE_NOTES="" if [ "$LLM_PROVIDER" = "anthropic" ]; then if ! RELEASE_NOTES=$(generate_with_anthropic); then if [ -n "${OPENAI_API_KEY:-}" ]; then echo "Anthropic generation failed, falling back to OpenAI..." >&2 if ! RELEASE_NOTES=$(generate_with_openai); then echo "Error: OpenAI fallback failed as well" >&2 exit 1 fi LLM_PROVIDER="openai" else echo "Error: Failed to generate release notes via Anthropic and no OpenAI fallback is available" >&2 exit 1 fi fi else if ! RELEASE_NOTES=$(generate_with_openai); then echo "Error: Failed to generate release notes via OpenAI" >&2 exit 1 fi fi # Output release notes echo "" echo "Generated release notes:" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "$RELEASE_NOTES" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" # Optionally save to file if [ "${SAVE_TO_FILE:-}" = "1" ]; then OUTPUT_FILE="release-notes-v${VERSION}.md" echo "$RELEASE_NOTES" > "$OUTPUT_FILE" echo "" echo "Saved to: $OUTPUT_FILE" fi