diff --git a/.gitignore b/.gitignore index de3252287..3ed669a39 100644 --- a/.gitignore +++ b/.gitignore @@ -96,4 +96,7 @@ lint/tmp/ # OSX .DS_Store -.kotlin \ No newline at end of file +.kotlin + +# Release notes generator output +gen-rn/output/ \ No newline at end of file diff --git a/gen-rn/README.md b/gen-rn/README.md new file mode 100644 index 000000000..1f3c41ff8 --- /dev/null +++ b/gen-rn/README.md @@ -0,0 +1,142 @@ +# gen-rn: XMTP SDK release notes generator + +AI-powered release notes generation for XMTP SDK releases using git history analysis and Claude AI. + +> [!IMPORTANT] +> Generated release notes must be reviewed by humans before publication. The AI provides a starting point, not a final product. + +## Purpose + +1. **Engineering sanity check**: Help engineers understand exactly what's included in a release +2. **Technical writing support**: Help technical writers with structured, well-organized release content +3. **Cross-SDK consistency**: Run in each XMTP SDK repo and compare structured outputs to gauge consistency of feature and fix implementations across SDKs + +## Prerequisites + +- Node.js 18 or later (required for native fetch API support) +- Anthropic API key: Get a key at: [https://console.anthropic.com/](https://console.anthropic.com/) + +## Configuration + +The script supports the following environment variables: + +- `ANTHROPIC_API_KEY` (required): Your Anthropic API key for Claude AI +- `MAX_TOKENS` (optional): Maximum tokens for AI response (default: 4000, range: 1000-8000) + +### API key handling + +- Never commit your `ANTHROPIC_API_KEY` to version control +- Store your API key in your shell profile as described in Setup +- Rotate your API key immediately if it's accidentally exposed or committed +- The temporary `ai-script.js` file is automatically deleted after execution for security + +## Setup + +```bash +# Set your API key +export ANTHROPIC_API_KEY="sk-ant-your-key-here" +echo 'export ANTHROPIC_API_KEY="your-key"' >> ~/.zshrc +source ~/.zshrc + +# Fetch latest tags +git fetch --tags + +# Make script executable +chmod +x gen-rn.sh + +# Optionally, set custom token limit for current terminal session +export MAX_TOKENS=6000 +``` + +## Usage + +### Basic usage + +```bash +# Auto-detect previous tag and generate delta +./gen-rn.sh 4.6.4 + +# Specify both tags to compare (what's new in 4.6.4 vs 4.6.3) +./gen-rn.sh 4.6.4 4.6.3 + +# Interactive mode - prompts for tag selection +# Detects previous tag and generate delta +./gen-rn.sh + +# Help +./gen-rn.sh --help +``` + +### Custom base comparison with `--from-base` + +Use the `--from-base` flag to compare a release against any git reference (branch, tag, or commit): + +```bash +# Show everything in 4.6.4 that's not in main +./gen-rn.sh 4.6.4 --from-base main + +# Show full content of a release candidate +./gen-rn.sh 4.6.4-rc.1 --from-base main + +# Show all changes since a specific older tag +./gen-rn.sh 4.6.4 --from-base 4.5.0 + +# Compare against a branch +./gen-rn.sh 4.6.4 --from-base release/4.6 +``` + +### Supported release types + +The script automatically detects release types based on tag naming: + +```bash +# Stable release (production-ready) +./gen-rn.sh 4.6.7 # → Release type: stable + +# Release candidate (pre-release testing) +./gen-rn.sh 4.6.7-rc.1 4.6.6 # → Release type: release-candidate + +# Development release (experimental) +./gen-rn.sh 4.6.7-dev.abc123 4.6.7 # → Release type: development +``` + +### Tag name validation + +The script validates all tag names to prevent command injection attacks. Tag names must contain only: +- Letters (a-z, A-Z) +- Numbers (0-9) +- Dots (.) +- Hyphens (-) +- Underscores (_) +- Forward slashes (/) + +## Output files + +After running, check the `output/` directory: + +```text +output/ +├── release-notes.md # Main AI-generated release notes +├── release-data.txt # Raw git analysis data (for debugging) +└── pr-refs.txt # PR numbers found in commits +``` + +> [!NOTE] +> - The `gen-rn/output/` directory is automatically excluded from version control via `.gitignore` +> - The temporary `ai-script.js` file is automatically deleted after execution for security + +## Troubleshooting + +### "Not in a git repository" + +- Run from the root of your XMTP SDK repository + +### "ANTHROPIC_API_KEY not set" + +- Set your API key: `export ANTHROPIC_API_KEY="your-key"` +- Get a key at: https://console.anthropic.com/ + +### "Could not compare tags" + +- Verify both tags exist: `git tag | grep 4.6.4` +- Use explicit tag names: `./gen-rn.sh 4.6.4 4.6.3` diff --git a/gen-rn/gen-rn.sh b/gen-rn/gen-rn.sh new file mode 100755 index 000000000..64a3f1254 --- /dev/null +++ b/gen-rn/gen-rn.sh @@ -0,0 +1,609 @@ +#!/bin/bash + +# XMTP SDK Release Notes Generator +# Generate AI-powered release notes for XMTP SDK releases using Claude + +set -euo pipefail + +# Configuration +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +OUTPUT_DIR="./output" +MAX_TOKENS="${MAX_TOKENS:-4000}" # Configurable via environment variable, default 4000 + +# Validate MAX_TOKENS is numeric and within range +if [[ "$MAX_TOKENS" =~ ^[0-9]+$ ]]; then + if [ "$MAX_TOKENS" -lt 1000 ] || [ "$MAX_TOKENS" -gt 8000 ]; then + echo "⚠ Warning: MAX_TOKENS should be between 1000-8000, got $MAX_TOKENS. Using default 4000." + MAX_TOKENS=4000 + fi +else + echo "⚠ Warning: MAX_TOKENS must be numeric, got '$MAX_TOKENS'. Using default 4000." + MAX_TOKENS=4000 +fi + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +print_header() { + echo -e "${BLUE}========================================${NC}" + echo -e "${BLUE}$1${NC}" + echo -e "${BLUE}========================================${NC}" +} + +print_success() { + echo -e "${GREEN}✓ $1${NC}" +} + +print_warning() { + echo -e "${YELLOW}⚠ $1${NC}" +} + +print_error() { + echo -e "${RED}✗ $1${NC}" +} + +# Function to validate tag name to prevent command injection +validate_tag_name() { + local tag="$1" + + # Allow alphanumeric, dots, hyphens, underscores, and forward slashes (for v-prefixed tags) + if [[ ! "$tag" =~ ^[a-zA-Z0-9._/-]+$ ]]; then + print_error "Invalid tag name: $tag" + echo "Tag names must contain only letters, numbers, dots, hyphens, underscores, and slashes" + exit 1 + fi + + # Check if tag exists in repository (verify it's actually a tag, not a branch or commit) + if ! git rev-parse -q --verify "refs/tags/$tag" >/dev/null 2>&1; then + print_error "Tag does not exist: $tag" + echo "Use 'git tag' to see available tags" + exit 1 + fi +} + +# Function to check prerequisites +check_prerequisites() { + print_header "Checking Prerequisites" + + # Check if we're in a git repository + if ! git rev-parse --git-dir > /dev/null 2>&1; then + print_error "Not in a git repository. Please run this from an XMTP SDK repository." + exit 1 + fi + + # Check for Node.js (for the AI script) + if ! command -v node &> /dev/null; then + print_error "Node.js is required but not installed." + echo "Please install Node.js 18 or later from https://nodejs.org/" + exit 1 + fi + + # Check Node.js version (requires 18+ for native fetch support) + NODE_VERSION=$(node --version | cut -d'v' -f2 | cut -d'.' -f1) + if ! [[ "$NODE_VERSION" =~ ^[0-9]+$ ]] || [ "$NODE_VERSION" -lt 18 ]; then + print_error "Node.js 18 or later is required (found: $(node --version))" + echo "The script uses native fetch() API which requires Node.js 18+" + echo "Please upgrade Node.js from https://nodejs.org/" + exit 1 + fi + + # Check for API key + if [ -z "${ANTHROPIC_API_KEY:-}" ]; then + print_error "ANTHROPIC_API_KEY environment variable is not set." + echo "Please set your API key: export ANTHROPIC_API_KEY=\"your-key-here\"" + exit 1 + fi + + # Check for jq (optional but recommended) + if ! command -v jq &> /dev/null; then + print_warning "jq is recommended for better output formatting." + echo "Install with: brew install jq (macOS) or sudo apt install jq (Ubuntu)" + fi + + print_success "Prerequisites check completed" +} + +# Function to get tags and setup environment +setup_environment() { + print_header "Setting Up Environment" + + # Create output directory + mkdir -p "$OUTPUT_DIR" + + # Get available tags + echo "Available git tags:" + echo "Recent tags:" + git tag --sort=-version:refname | grep -E "^[0-9]" | head -10 || true + echo "" + echo "Legacy tags:" + git tag --sort=-version:refname | grep "^v" | head -5 || true + echo "" + + # Get current tag or prompt user + if [ "${1:-}" != "" ]; then + TAG_NAME="$1" + else + echo "Enter the tag name to generate release notes for:" + read -r TAG_NAME + + if [ "$TAG_NAME" = "latest" ]; then + TAG_NAME=$(git tag --sort=-version:refname | head -n 1) + if [ -z "$TAG_NAME" ]; then + print_error "No tags found in repository" + exit 1 + fi + fi + fi + + # Check if user accidentally used --from-base as tag name + if [ "$TAG_NAME" = "--from-base" ]; then + print_error "Missing tag name before --from-base flag" + echo "Correct usage: ./gen-rn.sh --from-base " + echo "Example: ./gen-rn.sh 4.6.4 --from-base main" + exit 1 + fi + + # Validate tag name + validate_tag_name "$TAG_NAME" + + # Get previous tag with smart detection or --from-base option + PREVIOUS_TAG="" # Initialize to empty string for set -u compatibility + + # Check if --from-base flag is used + if [ "${2:-}" = "--from-base" ]; then + if [ -z "${3:-}" ]; then + print_error "--from-base requires a git reference (tag, branch, or commit)" + echo "Example: ./gen-rn.sh $TAG_NAME --from-base main" + exit 1 + fi + PREVIOUS_TAG="$3" + # Validate that the base reference exists in git + if ! git rev-parse "$PREVIOUS_TAG" >/dev/null 2>&1; then + print_error "Base reference does not exist: $PREVIOUS_TAG" + echo "Use 'git branch -a' or 'git tag' to see available references" + exit 1 + fi + echo "Using custom base reference: $PREVIOUS_TAG" + elif [ "${2:-}" != "" ]; then + PREVIOUS_TAG="$2" + # Validate previous tag + validate_tag_name "$PREVIOUS_TAG" + else + # Try to find the previous tag more intelligently + + # First, determine if current tag has 'v' prefix + if [[ "$TAG_NAME" == v* ]]; then + # Look for other v-prefixed tags (use grep -Fx for exact fixed-string matching) + PREVIOUS_TAG=$(git tag --sort=-version:refname | grep "^v" | grep -Fxv "$TAG_NAME" | head -n 1) + + # Guard against self-comparison + if [ "$PREVIOUS_TAG" = "$TAG_NAME" ]; then + PREVIOUS_TAG="" + fi + else + # Look for non-v-prefixed tags in the same version series + VERSION_SERIES=$(echo "$TAG_NAME" | cut -d. -f1-2) # e.g., "4.6" from "4.6.4" + + # Try to find previous tag in same series first (portable approach without grep -B) + PREVIOUS_TAG=$(git tag --sort=version:refname | grep "^${VERSION_SERIES}\." | { + local prev="" + while IFS= read -r tag; do + if [ "$tag" = "$TAG_NAME" ]; then + echo "$prev" + break + fi + prev="$tag" + done + }) + + # If no same-series tag found, try broader search + if [ -z "$PREVIOUS_TAG" ]; then + MAJOR_VERSION=$(echo "$TAG_NAME" | cut -d. -f1) # e.g., "4" from "4.6.4" + PREVIOUS_TAG=$(git tag --sort=version:refname | grep "^${MAJOR_VERSION}\." | { + local prev="" + while IFS= read -r tag; do + if [ "$tag" = "$TAG_NAME" ]; then + echo "$prev" + break + fi + prev="$tag" + done + }) + fi + + # If still no tag, fall back to any non-v tag + if [ -z "$PREVIOUS_TAG" ]; then + PREVIOUS_TAG=$(git tag --sort=-version:refname | grep -v "^v" | grep -Fxv "$TAG_NAME" | head -n 1) + fi + fi + + if [ -z "$PREVIOUS_TAG" ]; then + # Get first root commit (handles repos with multiple root commits) + PREVIOUS_TAG=$(git rev-list --max-parents=0 HEAD | head -n 1) + print_warning "No previous tag found, comparing against first commit" + else + echo "Using previous tag: $PREVIOUS_TAG" + fi + fi + + # Prevent self-comparison + if [ "$TAG_NAME" = "$PREVIOUS_TAG" ]; then + print_error "Cannot compare tag to itself: $TAG_NAME" + echo "Please specify a different previous tag" + exit 1 + fi + + # Determine release type + if [[ "$TAG_NAME" == *"rc"* ]]; then + RELEASE_TYPE="release-candidate" + elif [[ "$TAG_NAME" == *"dev"* ]]; then + RELEASE_TYPE="development" + else + RELEASE_TYPE="stable" + fi + + echo "" + print_success "Environment configured:" + echo " Repository: $(basename "$(git rev-parse --show-toplevel)")" + echo " Current tag: $TAG_NAME" + echo " Previous tag: $PREVIOUS_TAG" + echo " Release type: $RELEASE_TYPE" + echo " Output directory: $OUTPUT_DIR" +} + +# Function to gather release information +gather_release_info() { + print_header "Gathering Release Information" + + local info_file="$OUTPUT_DIR/release-data.txt" + + echo "=== XMTP SDK Release Analysis ===" > "$info_file" + echo "Repository: $(basename "$(git rev-parse --show-toplevel)")" >> "$info_file" + echo "Current tag: $TAG_NAME" >> "$info_file" + echo "Previous tag: $PREVIOUS_TAG" >> "$info_file" + echo "Release type: $RELEASE_TYPE" >> "$info_file" + echo "Generated: $(date)" >> "$info_file" + echo "" >> "$info_file" + + # Get commit messages between tags + echo "=== COMMIT MESSAGES ===" >> "$info_file" + if git rev-list "$PREVIOUS_TAG..$TAG_NAME" &>/dev/null; then + if ! git log "$PREVIOUS_TAG..$TAG_NAME" --pretty=format:"- %s (%an, %ad)" --date=short >> "$info_file" 2>&1; then + print_warning "Failed to get commit messages between tags" + echo "Error retrieving commit messages" >> "$info_file" + fi + else + print_warning "Could not compare tags, using last 10 commits" + if ! git log -10 --pretty=format:"- %s (%an, %ad)" --date=short >> "$info_file" 2>&1; then + print_error "Failed to get commit messages" + echo "Error retrieving commit messages" >> "$info_file" + fi + fi + echo -e "\n" >> "$info_file" + + # Get changed files with stats + echo "=== CHANGED FILES ===" >> "$info_file" + if git rev-list "$PREVIOUS_TAG..$TAG_NAME" &>/dev/null; then + if ! git diff --name-status "$PREVIOUS_TAG..$TAG_NAME" >> "$info_file" 2>&1; then + print_warning "Failed to get changed files between tags" + echo "Error retrieving changed files" >> "$info_file" + fi + else + if ! git diff --name-status HEAD~10..HEAD >> "$info_file" 2>&1; then + print_warning "Failed to get changed files" + echo "Error retrieving changed files" >> "$info_file" + fi + fi + echo -e "\n" >> "$info_file" + + # Get detailed diff for key files (Android-specific) + echo "=== KEY FILE CHANGES ===" >> "$info_file" + for file in build.gradle settings.gradle gradle.properties library/build.gradle example/build.gradle README.md CHANGELOG.md; do + if [ -f "$file" ]; then + if git rev-list "$PREVIOUS_TAG..$TAG_NAME" &>/dev/null; then + if git diff --name-only "$PREVIOUS_TAG..$TAG_NAME" | grep -Fxq "$file"; then + echo "Changes in $file:" >> "$info_file" + if ! git diff "$PREVIOUS_TAG..$TAG_NAME" -- "$file" >> "$info_file" 2>&1; then + print_warning "Failed to get diff for $file" + echo "Error retrieving diff for $file" >> "$info_file" + fi + echo -e "\n" >> "$info_file" + fi + fi + fi + done + + # Get PR information if available + echo "=== PULL REQUEST REFERENCES ===" >> "$info_file" + if git rev-list "$PREVIOUS_TAG..$TAG_NAME" &>/dev/null; then + git log "$PREVIOUS_TAG..$TAG_NAME" --pretty=format:"%s" | grep -oE "#[0-9]+" | sort -u > "$OUTPUT_DIR/pr-refs.txt" 2>/dev/null || touch "$OUTPUT_DIR/pr-refs.txt" + else + git log -10 --pretty=format:"%s" | grep -oE "#[0-9]+" | sort -u > "$OUTPUT_DIR/pr-refs.txt" 2>/dev/null || touch "$OUTPUT_DIR/pr-refs.txt" + fi + + if [ -s "$OUTPUT_DIR/pr-refs.txt" ]; then + echo "PR numbers found in commits:" >> "$info_file" + cat "$OUTPUT_DIR/pr-refs.txt" >> "$info_file" + else + echo "No PR references found in commit messages" >> "$info_file" + fi + + local filesize=$(wc -c < "$info_file") + print_success "Release information gathered ($filesize bytes)" + echo " Data file: $info_file" + echo " PR refs: $OUTPUT_DIR/pr-refs.txt" +} + +# Function to generate AI release notes +generate_ai_release_notes() { + print_header "Generating AI Release Notes" + + local info_file="$OUTPUT_DIR/release-data.txt" + local ai_script="$OUTPUT_DIR/ai-script.js" + + # Create the AI generation script + cat > "$ai_script" << 'EOF' +const fs = require('fs'); +const path = require('path'); + +async function generateReleaseNotes() { + const outputDir = process.env.OUTPUT_DIR || './output'; + const releaseInfo = fs.readFileSync(path.join(outputDir, 'release-data.txt'), 'utf8'); + + // Truncate if too long (API limits) + const maxLength = 50000; + const truncatedInfo = releaseInfo.length > maxLength + ? releaseInfo.substring(0, maxLength) + "\n\n[Content truncated due to length...]" + : releaseInfo; + + const prompt = `Analyze the following XMTP SDK release information and generate comprehensive release notes. + +CONTEXT: +- XMTP is a messaging protocol for web3 applications +- This is for SDK releases used by developers building messaging applications +- The audience includes: engineers making releases, technical writers, and cross-SDK comparison +- Focus on developer-facing changes, new features, bug fixes, and breaking changes + +RELEASE INFORMATION: +${truncatedInfo} + +Please generate release notes that include: + +1. **Release Summary** (2-3 sentences about what this release contains) + +2. **New Features** (if any) +- List new capabilities added +- Explain developer benefits and use cases + +3. **Improvements & Enhancements** (if any) +- Performance improvements +- Developer experience enhancements +- API improvements + +4. **Bug Fixes** (if any) +- Issues resolved +- Stability improvements + +5. **Breaking Changes** (if any) +- API changes that require developer action +- Clear migration guidance + +6. **Dependencies & Infrastructure** (if any) +- Dependency updates +- Build or tooling changes +- Library version updates + +7. **Developer Notes** (if any) +- Important implementation details +- Testing recommendations +- Known limitations + +FORMAT REQUIREMENTS: +- Use clear markdown formatting +- Be specific about technical changes but explain them in plain English +- Focus on changes that matter to developers integrating this SDK +- If certain sections don't apply, omit those sections +- Include code examples for breaking changes where helpful +- Avoid overly technical git details unless relevant to SDK users`; + + try { + console.log("🤖 Generating release notes with AI..."); + console.log(`📄 Prompt length: ${prompt.length} characters`); + + const response = await fetch("https://api.anthropic.com/v1/messages", { + method: "POST", + headers: { + "Content-Type": "application/json", + "x-api-key": process.env.ANTHROPIC_API_KEY, + "anthropic-version": "2023-06-01", + }, + body: JSON.stringify({ + model: "claude-sonnet-4-20250514", + max_tokens: parseInt(process.env.MAX_TOKENS) || 4000, + messages: [ + { role: "user", content: prompt } + ], + }) + }); + + console.log(`📡 API Response status: ${response.status}`); + + if (!response.ok) { + const errorText = await response.text(); + + // Provide actionable error messages for common failures + if (response.status === 401) { + throw new Error(`Authentication failed (401). Check your ANTHROPIC_API_KEY is valid and not expired.`); + } else if (response.status === 429) { + throw new Error(`Rate limit exceeded (429). Wait a few minutes and try again, or reduce MAX_TOKENS.`); + } else if (response.status === 400) { + throw new Error(`Bad request (400). This may be due to invalid MAX_TOKENS or request format. Error: ${errorText.substring(0, 200)}`); + } else { + throw new Error(`HTTP error! status: ${response.status}, body: ${errorText.substring(0, 300)}`); + } + } + + const data = await response.json(); + + // Validate API response structure + const content = Array.isArray(data?.content) ? data.content : null; + if (!content || content.length === 0) { + const responsePreview = JSON.stringify(data).substring(0, 200); + throw new Error(`Unexpected API response: missing or invalid 'content' array. Response: ${responsePreview}`); + } + + const releaseNotes = content + .map(item => (item && item.type === "text" ? item.text : "")) + .filter(Boolean) + .join("\n"); + + // Save release notes + const outputPath = path.join(outputDir, 'release-notes.md'); + fs.writeFileSync(outputPath, releaseNotes); + console.log("✅ Release notes generated successfully"); + + } catch (error) { + console.error('❌ Error generating release notes:', error.message); + + const fallbackNotes = `# Release Notes for ${process.env.TAG_NAME || 'Unknown Tag'} + +**⚠️ AI generation failed - please create release notes manually** + +Error: ${error.message} + +## Changes in this release +See commit history between ${process.env.PREVIOUS_TAG || 'unknown'} and ${process.env.TAG_NAME || 'unknown'} + +## Raw release information: +\`\`\` +${truncatedInfo.substring(0, 2000)} +\`\`\` + +--- +*Note: This is fallback content. Please review git history and create proper release notes.* +`; + const fallbackPath = path.join(outputDir, 'release-notes.md'); + fs.writeFileSync(fallbackPath, fallbackNotes); + console.log("📁 Fallback release notes created"); + process.exit(1); + } +} + +// Set environment variables for the script +process.env.TAG_NAME = process.argv[2] || process.env.TAG_NAME; +process.env.PREVIOUS_TAG = process.argv[3] || process.env.PREVIOUS_TAG; +process.env.RELEASE_TYPE = process.argv[4] || process.env.RELEASE_TYPE; + +generateReleaseNotes(); +EOF + + # Set environment variables for the Node.js script + export TAG_NAME="$TAG_NAME" + export PREVIOUS_TAG="$PREVIOUS_TAG" + export RELEASE_TYPE="$RELEASE_TYPE" + export OUTPUT_DIR="$OUTPUT_DIR" + export MAX_TOKENS="$MAX_TOKENS" + + # Run the AI generation + if node "$ai_script" "$TAG_NAME" "$PREVIOUS_TAG" "$RELEASE_TYPE"; then + print_success "AI release notes generated successfully" + else + print_warning "AI generation failed, but fallback content was created" + fi + + # Clean up temporary AI script for security + rm -f "$ai_script" +} + +# Function to display results +display_results() { + print_header "Release Notes Generated" + + echo "📁 Generated files in $OUTPUT_DIR/:" + # List files with sizes (handles filenames with spaces correctly) + for file in "$OUTPUT_DIR"/*; do + if [ -f "$file" ]; then + size=$(wc -c < "$file") + printf " %s (%s bytes)\n" "$(basename "$file")" "$size" + fi + done + echo "" + + if [ -f "$OUTPUT_DIR/release-notes.md" ]; then + echo "📝 Release Notes Preview:" + echo "========================" + head -30 "$OUTPUT_DIR/release-notes.md" + echo "" + echo "[... view complete release notes in $OUTPUT_DIR/release-notes.md ...]" + echo "" + fi + + print_success "Release notes generation completed!" + echo "" + echo "📋 Next steps:" + echo "1. Review the release notes: $OUTPUT_DIR/release-notes.md" + echo "2. Edit/customize as needed for your release" + echo "3. Use the notes in your GitHub release or documentation" + echo "" + echo "📊 For debugging or customization:" + echo "- Raw data analyzed: $OUTPUT_DIR/release-data.txt" + echo "- PR references found: $OUTPUT_DIR/pr-refs.txt" +} + +# Main execution +main() { + echo "🚀 XMTP SDK Release Notes Generator" + echo "===================================" + echo "" + + check_prerequisites + setup_environment "${1:-}" "${2:-}" "${3:-}" + gather_release_info + generate_ai_release_notes + display_results +} + +# Script usage help +if [ "${1:-}" = "--help" ] || [ "${1:-}" = "-h" ]; then + echo "XMTP SDK Release Notes Generator" + echo "" + echo "Generates AI-powered release notes for XMTP SDK releases using git history analysis." + echo "" + echo "Usage:" + echo " $0 [tag_name] [previous_tag]" + echo " $0 [tag_name] --from-base " + echo " $0 --help|-h" + echo "" + echo "Examples:" + echo " $0 # Interactive mode - will prompt for tag" + echo " $0 4.6.4 # Generate notes for 4.6.4 (auto-detect previous)" + echo " $0 4.6.4 4.6.3 # Compare 4.6.4 to 4.6.3 (what's new)" + echo " $0 4.6.4 --from-base main # Show everything in 4.6.4 not in main" + echo " $0 4.6.4 --from-base 4.5.0 # Show everything since 4.5.0" + echo " $0 4.6.4-rc.1 --from-base main # Full content of RC release" + echo " $0 latest # Generate notes for most recent tag" + echo "" + echo "Requirements:" + echo " - Git repository with release tags" + echo " - Node.js 18 or later (for native fetch API support)" + echo " - ANTHROPIC_API_KEY environment variable set" + echo " - Internet connection (for AI API)" + echo "" + echo "Environment Variables:" + echo " ANTHROPIC_API_KEY Required: Your Anthropic API key" + echo " MAX_TOKENS Optional: Max tokens for AI response (default: 4000, range: 1000-8000)" + echo "" + echo "Output:" + echo " - output/release-notes.md # Main AI-generated release notes" + echo " - output/release-data.txt # Raw git analysis data" + echo " - output/pr-refs.txt # PR references found" + echo "" + exit 0 +fi + +# Run the main function +main "$@" \ No newline at end of file