Spaces:
Sleeping
Sleeping
| # Ensure we're running in bash | |
| if [ -z "$BASH_VERSION" ]; then | |
| echo "This script requires bash. Please run with: bash $0" >&2 | |
| exit 1 | |
| fi | |
| # Ensure we're using bash 4.0 or later for associative arrays | |
| if ((BASH_VERSINFO[0] < 4)); then | |
| echo "This script requires bash version 4 or later" >&2 | |
| echo "Current bash version: $BASH_VERSION" >&2 | |
| exit 1 | |
| fi | |
| # Set default values for required environment variables if not in GitHub Actions | |
| if [ -z "$GITHUB_ACTIONS" ]; then | |
| : "${GITHUB_SERVER_URL:=https://github.com}" | |
| : "${GITHUB_REPOSITORY:=stackblitz-labs/bolt.diy}" | |
| : "${GITHUB_OUTPUT:=/tmp/github_output}" | |
| touch "$GITHUB_OUTPUT" | |
| # Running locally | |
| echo "Running locally - checking for upstream remote..." | |
| MAIN_REMOTE="origin" | |
| if git remote -v | grep -q "upstream"; then | |
| MAIN_REMOTE="upstream" | |
| fi | |
| MAIN_BRANCH="main" # or "master" depending on your repository | |
| # Ensure we have latest tags | |
| git fetch ${MAIN_REMOTE} --tags | |
| # Use the remote reference for git log | |
| GITLOG_REF="${MAIN_REMOTE}/${MAIN_BRANCH}" | |
| else | |
| # Running in GitHub Actions | |
| GITLOG_REF="HEAD" | |
| fi | |
| # Get the latest tag | |
| LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") | |
| # Start changelog file | |
| echo "# π Release v${NEW_VERSION}" > changelog.md | |
| echo "" >> changelog.md | |
| echo "## What's Changed π" >> changelog.md | |
| echo "" >> changelog.md | |
| if [ -z "$LATEST_TAG" ]; then | |
| echo "### π First Release" >> changelog.md | |
| echo "" >> changelog.md | |
| echo "Exciting times! This marks our first release. Thanks to everyone who contributed! π" >> changelog.md | |
| echo "" >> changelog.md | |
| COMPARE_BASE="$(git rev-list --max-parents=0 HEAD)" | |
| else | |
| echo "### π Changes since $LATEST_TAG" >> changelog.md | |
| echo "" >> changelog.md | |
| COMPARE_BASE="$LATEST_TAG" | |
| fi | |
| # Function to extract conventional commit type and associated emoji | |
| get_commit_type() { | |
| local msg="$1" | |
| if [[ $msg =~ ^feat(\(.+\))?:|^feature(\(.+\))?: ]]; then echo "β¨ Features" | |
| elif [[ $msg =~ ^fix(\(.+\))?: ]]; then echo "π Bug Fixes" | |
| elif [[ $msg =~ ^docs(\(.+\))?: ]]; then echo "π Documentation" | |
| elif [[ $msg =~ ^style(\(.+\))?: ]]; then echo "π Styles" | |
| elif [[ $msg =~ ^refactor(\(.+\))?: ]]; then echo "β»οΈ Code Refactoring" | |
| elif [[ $msg =~ ^perf(\(.+\))?: ]]; then echo "β‘ Performance Improvements" | |
| elif [[ $msg =~ ^test(\(.+\))?: ]]; then echo "π§ͺ Tests" | |
| elif [[ $msg =~ ^build(\(.+\))?: ]]; then echo "π οΈ Build System" | |
| elif [[ $msg =~ ^ci(\(.+\))?: ]]; then echo "βοΈ CI" | |
| elif [[ $msg =~ ^chore(\(.+\))?: ]]; then echo "" # Skip chore commits | |
| else echo "π Other Changes" # Default category with emoji | |
| fi | |
| } | |
| # Initialize associative arrays | |
| declare -A CATEGORIES | |
| declare -A COMMITS_BY_CATEGORY | |
| declare -A ALL_AUTHORS | |
| declare -A NEW_CONTRIBUTORS | |
| # Get all historical authors before the compare base | |
| while IFS= read -r author; do | |
| ALL_AUTHORS["$author"]=1 | |
| done < <(git log "${COMPARE_BASE}" --pretty=format:"%ae" | sort -u) | |
| # Process all commits since last tag | |
| while IFS= read -r commit_line; do | |
| if [[ ! $commit_line =~ ^[a-f0-9]+\| ]]; then | |
| echo "WARNING: Skipping invalid commit line format: $commit_line" >&2 | |
| continue | |
| fi | |
| HASH=$(echo "$commit_line" | cut -d'|' -f1) | |
| COMMIT_MSG=$(echo "$commit_line" | cut -d'|' -f2) | |
| BODY=$(echo "$commit_line" | cut -d'|' -f3) | |
| # Skip if hash doesn't match the expected format | |
| if [[ ! $HASH =~ ^[a-f0-9]{40}$ ]]; then | |
| continue | |
| fi | |
| HASH=$(echo "$commit_line" | cut -d'|' -f1) | |
| COMMIT_MSG=$(echo "$commit_line" | cut -d'|' -f2) | |
| BODY=$(echo "$commit_line" | cut -d'|' -f3) | |
| # Validate hash format | |
| if [[ ! $HASH =~ ^[a-f0-9]{40}$ ]]; then | |
| echo "WARNING: Invalid commit hash format: $HASH" >&2 | |
| continue | |
| fi | |
| # Check if it's a merge commit | |
| if [[ $COMMIT_MSG =~ Merge\ pull\ request\ #([0-9]+) ]]; then | |
| # echo "Processing as merge commit" >&2 | |
| PR_NUM="${BASH_REMATCH[1]}" | |
| # Extract the PR title from the merge commit body | |
| PR_TITLE=$(echo "$BODY" | grep -v "^Merge pull request" | head -n 1) | |
| # Only process if it follows conventional commit format | |
| CATEGORY=$(get_commit_type "$PR_TITLE") | |
| if [ -n "$CATEGORY" ]; then # Only process if it's a conventional commit | |
| # Get PR author's GitHub username | |
| GITHUB_USERNAME=$(gh pr view "$PR_NUM" --json author --jq '.author.login') | |
| if [ -n "$GITHUB_USERNAME" ]; then | |
| # Check if this is a first-time contributor | |
| AUTHOR_EMAIL=$(git show -s --format='%ae' "$HASH") | |
| if [ -z "${ALL_AUTHORS[$AUTHOR_EMAIL]}" ]; then | |
| NEW_CONTRIBUTORS["$GITHUB_USERNAME"]=1 | |
| ALL_AUTHORS["$AUTHOR_EMAIL"]=1 | |
| fi | |
| CATEGORIES["$CATEGORY"]=1 | |
| COMMITS_BY_CATEGORY["$CATEGORY"]+="* ${PR_TITLE#*: } ([#$PR_NUM](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/pull/$PR_NUM)) by @$GITHUB_USERNAME"$'\n' | |
| else | |
| COMMITS_BY_CATEGORY["$CATEGORY"]+="* ${PR_TITLE#*: } ([#$PR_NUM](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/pull/$PR_NUM))"$'\n' | |
| fi | |
| fi | |
| # Check if it's a squash merge by looking for (#NUMBER) pattern | |
| elif [[ $COMMIT_MSG =~ \(#([0-9]+)\) ]]; then | |
| # echo "Processing as squash commit" >&2 | |
| PR_NUM="${BASH_REMATCH[1]}" | |
| # Only process if it follows conventional commit format | |
| CATEGORY=$(get_commit_type "$COMMIT_MSG") | |
| if [ -n "$CATEGORY" ]; then # Only process if it's a conventional commit | |
| # Get PR author's GitHub username | |
| GITHUB_USERNAME=$(gh pr view "$PR_NUM" --json author --jq '.author.login') | |
| if [ -n "$GITHUB_USERNAME" ]; then | |
| # Check if this is a first-time contributor | |
| AUTHOR_EMAIL=$(git show -s --format='%ae' "$HASH") | |
| if [ -z "${ALL_AUTHORS[$AUTHOR_EMAIL]}" ]; then | |
| NEW_CONTRIBUTORS["$GITHUB_USERNAME"]=1 | |
| ALL_AUTHORS["$AUTHOR_EMAIL"]=1 | |
| fi | |
| CATEGORIES["$CATEGORY"]=1 | |
| COMMIT_TITLE=${COMMIT_MSG%% (#*} # Remove the PR number suffix | |
| COMMIT_TITLE=${COMMIT_TITLE#*: } # Remove the type prefix | |
| COMMITS_BY_CATEGORY["$CATEGORY"]+="* $COMMIT_TITLE ([#$PR_NUM](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/pull/$PR_NUM)) by @$GITHUB_USERNAME"$'\n' | |
| else | |
| COMMIT_TITLE=${COMMIT_MSG%% (#*} # Remove the PR number suffix | |
| COMMIT_TITLE=${COMMIT_TITLE#*: } # Remove the type prefix | |
| COMMITS_BY_CATEGORY["$CATEGORY"]+="* $COMMIT_TITLE ([#$PR_NUM](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/pull/$PR_NUM))"$'\n' | |
| fi | |
| fi | |
| else | |
| # echo "Processing as regular commit" >&2 | |
| # Process conventional commits without PR numbers | |
| CATEGORY=$(get_commit_type "$COMMIT_MSG") | |
| if [ -n "$CATEGORY" ]; then # Only process if it's a conventional commit | |
| # Get commit author info | |
| AUTHOR_EMAIL=$(git show -s --format='%ae' "$HASH") | |
| # Try to get GitHub username using gh api | |
| if [ -n "$GITHUB_ACTIONS" ] || command -v gh >/dev/null 2>&1; then | |
| GITHUB_USERNAME=$(gh api "/repos/${GITHUB_REPOSITORY}/commits/${HASH}" --jq '.author.login' 2>/dev/null) | |
| fi | |
| if [ -n "$GITHUB_USERNAME" ]; then | |
| # If we got GitHub username, use it | |
| if [ -z "${ALL_AUTHORS[$AUTHOR_EMAIL]}" ]; then | |
| NEW_CONTRIBUTORS["$GITHUB_USERNAME"]=1 | |
| ALL_AUTHORS["$AUTHOR_EMAIL"]=1 | |
| fi | |
| CATEGORIES["$CATEGORY"]=1 | |
| COMMIT_TITLE=${COMMIT_MSG#*: } # Remove the type prefix | |
| COMMITS_BY_CATEGORY["$CATEGORY"]+="* $COMMIT_TITLE (${HASH:0:7}) by @$GITHUB_USERNAME"$'\n' | |
| else | |
| # Fallback to git author name if no GitHub username found | |
| AUTHOR_NAME=$(git show -s --format='%an' "$HASH") | |
| if [ -z "${ALL_AUTHORS[$AUTHOR_EMAIL]}" ]; then | |
| NEW_CONTRIBUTORS["$AUTHOR_NAME"]=1 | |
| ALL_AUTHORS["$AUTHOR_EMAIL"]=1 | |
| fi | |
| CATEGORIES["$CATEGORY"]=1 | |
| COMMIT_TITLE=${COMMIT_MSG#*: } # Remove the type prefix | |
| COMMITS_BY_CATEGORY["$CATEGORY"]+="* $COMMIT_TITLE (${HASH:0:7}) by $AUTHOR_NAME"$'\n' | |
| fi | |
| fi | |
| fi | |
| done < <(git log "${COMPARE_BASE}..${GITLOG_REF}" --pretty=format:"%H|%s|%b" --reverse --first-parent) | |
| # Write categorized commits to changelog with their emojis | |
| for category in "β¨ Features" "π Bug Fixes" "π Documentation" "π Styles" "β»οΈ Code Refactoring" "β‘ Performance Improvements" "π§ͺ Tests" "π οΈ Build System" "βοΈ CI" "π Other Changes"; do | |
| if [ -n "${COMMITS_BY_CATEGORY[$category]}" ]; then | |
| echo "### $category" >> changelog.md | |
| echo "" >> changelog.md | |
| echo "${COMMITS_BY_CATEGORY[$category]}" >> changelog.md | |
| echo "" >> changelog.md | |
| fi | |
| done | |
| # Add first-time contributors section if there are any | |
| if [ ${#NEW_CONTRIBUTORS[@]} -gt 0 ]; then | |
| echo "## β¨ First-time Contributors" >> changelog.md | |
| echo "" >> changelog.md | |
| echo "A huge thank you to our amazing new contributors! Your first contribution marks the start of an exciting journey! π" >> changelog.md | |
| echo "" >> changelog.md | |
| # Use readarray to sort the keys | |
| readarray -t sorted_contributors < <(printf '%s\n' "${!NEW_CONTRIBUTORS[@]}" | sort) | |
| for github_username in "${sorted_contributors[@]}"; do | |
| echo "* π [@$github_username](https://github.com/$github_username)" >> changelog.md | |
| done | |
| echo "" >> changelog.md | |
| fi | |
| # Add compare link if not first release | |
| if [ -n "$LATEST_TAG" ]; then | |
| echo "## π Stats" >> changelog.md | |
| echo "" >> changelog.md | |
| echo "**Full Changelog**: [\`$LATEST_TAG..v${NEW_VERSION}\`](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/compare/$LATEST_TAG...v${NEW_VERSION})" >> changelog.md | |
| fi | |
| # Output the changelog content | |
| CHANGELOG_CONTENT=$(cat changelog.md) | |
| { | |
| echo "content<<EOF" | |
| echo "$CHANGELOG_CONTENT" | |
| echo "EOF" | |
| } >> "$GITHUB_OUTPUT" | |
| # Also print to stdout for local testing | |
| echo "Generated changelog:" | |
| echo "===================" | |
| cat changelog.md | |
| echo "===================" |